码迷,mamicode.com
首页 > 其他好文 > 详细

树链剖分

时间:2018-09-25 23:01:40      阅读:343      评论:0      收藏:0      [点我收藏+]

标签:orm   The   就是   sid   数据   数组   std   树状数组   get   

 

树链剖分就是将树分割成多条链,然后利用数据结构(线段树、树状数组等)来维护这些链。

首先就是一些必须知道的概念:

  • 重结点:子树结点数目最多的结点;
  • 轻节点:父亲节点中除了重结点以外的结点;
  • 重边:父亲结点和重结点连成的边;
  • 轻边:父亲节点和轻节点连成的边;
  • 重链:由多条重边连接而成的路径;
  • 轻链:由多条轻边连接而成的路径;

技术分享图片

比如上面这幅图中,用黑线连接的结点都是重结点,其余均是轻结点,2-11、1-11就是重链,其他就是轻链,用红点标记的就是该结点所在链的起点,也就是我们??提到的top结点,还有每条边的值其实是进行dfs时的执行序号。

算法中定义了以下的数组用来存储上边提到的概念:

名称解释
siz[u] 保存以u为根的子树节点个数
top[u] 保存当前节点所在链的顶端节点
son[u] 保存重儿子
dep[u] 保存结点u的深度值
faz[u] 保存结点u的父亲节点
tid[u] 保存树中每个节点剖分以后的新编号(DFS的执行顺序)
rnk[u] 保存当前节点在树中的位置

 

除此之外,还包括两种性质:

  1. 如果(u, v)是一条轻边,那么size(v) < size(u)/2;
  2. 从根结点到任意结点的路所经过的轻重链的个数必定都小与O(logn);

 

首先定义以下数组:

 
 
 
 
 
 
const int MAXN = (100000 << 2) + 10;
?
//Heavy-light Decomposition STARTS FORM HERE
int siz[MAXN];//number of son
int top[MAXN];//top of the heavy link
int son[MAXN];//heavy son of the node
int dep[MAXN];//depth of the node
int faz[MAXN];//father of the node
int tid[MAXN];//ID -> DFSID
int rnk[MAXN];//DFSID -> ID

算法大致需要进行两次的DFS,第一次DFS可以得到当前节点的父亲结点(faz数组)、当前结点的深度值(dep数组)、当前结点的子结点数量(size数组)、当前结点的重结点(son数组)

void dfs1(int u, int father, int depth) {
    /*
     * u: 当前结点
     * father: 父亲结点
     * depth: 深度
     */
    // 更新dep、faz、siz数组
    dep[u] = depth;
    faz[u] = father;
    siz[u] = 1;
?
    // 遍历所有和当前结点连接的结点
    for (int i = head[u]; i; i = edg[i].next) {
        int v = edg[i].to;
        // 如果连接的结点是当前结点的父亲结点,则不处理
        if (v != faz[u]) {
            dfs1(v, u, depth + 1);
            // 收敛的时候将当前结点的siz加上子结点的siz
            siz[u] += siz[v];
            // 如果没有设置过重结点son或者子结点v的siz大于之前记录的重结点son,则进行更新
            if (son[u] == -1 || siz[v] > siz[son[u]]) {
                son[u] = v;
            }
        }
    }
}

第二次DFS的时候则可以将各个重结点连接成重链,轻节点连接成轻链,并且将重链(其实就是一段区间)用数据结构(一般是树状数组或线段树)

来进行维护,并且为每个节点进行编号,其实就是DFS在执行时的顺序(tid数组),以及当前节点所在链的起点(top数组),还有当前节点在树中

的位置(rank数组)。

void dfs2(int u, int t) {
    /**
     * u:当前结点
     * t:起始的重结点
     */
    top[u] = t;  // 设置当前结点的起点为t
    tid[u] = cnt;  // 设置当前结点的dfs执行序号
    rnk[cnt] = u;  // 设置dfs序号对应成当前结点
    cnt++;
?
    // 如果当前结点没有处在重链上,则不处理
    if (son[u] == -1) {
        return;
    }
    // 将这条重链上的所有的结点都设置成起始的重结点
    dfs2(son[u], t);
    // 遍历所有和当前结点连接的结点
    for (int i = head[u]; i; i = edg[i].next) {
        int v = edg[i].to;
        // 如果连接结点不是当前结点的重子结点并且也不是u的父亲结点,则将其的top设置成自己,进一步递归
        if (v != son[u] && v != faz[u]){
            dfs2(v, v);
        }
    }
}

而修改和查询操作原理是类似的,以查询操作为例,其实就是个LCA,不过这里使用了top来进行加速,因为top可以直接跳转到该重链的起始结点,

轻链没有起始结点之说,他们的top就是自己。需要注意的是,每次循环只能跳一次,并且让结点深的那个来跳到top的位置,避免两个一起跳从而插肩而过。

树链剖分模板:
//树链剖分 
//query() update()数据结构的操作 
#include<bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof a)
#define mp make_pair
#define eps 1e-8
typedef long long ll;
typedef unsigned long long ull; 
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3fll;
const int maxn=(100000<<2)+10; 
int siz[maxn],top[maxn],son[maxn],dep[maxn];
int fa[maxn],tid[maxn],rnk[maxn],cnt;
int head[maxn],tot;

struct Node{
	int to,nxt;
} edg[maxn<<2];

void addedge(int u,int v)
{
	edg[tot].to=v;
	edg[tot].nxt=head[u];
	head[u]=tot++;
}

void dfs1(int u,int father,int depth) //处理出每个点的深度,重儿子,父亲节点以及以它为根的子节点的数量 
{
    dep[u]=depth;
    fa[u]=father;
    siz[u]=1;
    for(int i=head[u];i;i=edg[i].nxt) 
	{
        int v=edg[i].to;
        if(v!=fa[u]) 
		{
            dfs1(v,u,depth+1);
            siz[u]+=siz[v];
            if(son[u]==-1||siz[v]>siz[son[u]]) son[u]=v;
        }
    }
}

void dfs2(int u, int t) //getpos()
{
    top[u]=t;  
    tid[u]=cnt; rnk[cnt]=u; //用于数据结构中位置的还原 
    cnt++;
    if(son[u]==-1) return;
    dfs2(son[u],t);
    for(int i=head[u];i;i=edg[i].nxt) 
	{
        int v=edg[i].to;
        if(v!=son[u] && v!=fa[u]) dfs2(v,v);
    }
}

int query_path(int x, int y) //查询结点x到结点y的路径和
{
    int ans=0;
    int fx=top[x],fy=top[y];
    while(fx!=fy) 
	{
        if(dep[fx]>=dep[fy]) ans+=query(1,tid[fx],tid[x]),x=fa[fx]; 
		else ans+=query(1,tid[fy],tid[y]),y=fa[fy];
        fx=top[x],fy=top[y];
    }
    if(x!=y) 
	{
        if(tid[x]<tid[y]) ans+=query(1,tid[x],tid[y]); 
		else ans+=query(1,tid[y],tid[x]);
    } 
	else ans+=query(1,tid[x],tid[y]);
    return ans;
}

void update_path(int x,int y,int z) //更新结点x到结点y的值 +z 
{
    int fx=top[x],fy=top[y];
    while(fx!=fy) 
	{
        if(dep[fx]>dep[fy]) update(1,tid[fx],tid[x],z),x=fa[fx];
		else update(1,tid[fy],tid[y],z),y=fa[fy];
        fx=top[x],fy=top[y];
    }
    if(x!=y)
    {
    	if(tid[x]<tid[y]) update(1,tid[x],tid[y],z);
        else update(1,tid[y],tid[x],z);	
	}
    else update(1,tid[x],tid[y],z);
}

  

 

树链剖分

标签:orm   The   就是   sid   数据   数组   std   树状数组   get   

原文地址:https://www.cnblogs.com/songorz/p/9703848.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!