总结-树链剖分

来源:互联网 发布:java源代码文件调试 编辑:程序博客网 时间:2024/06/06 03:19

总结-树链剖分


(今天学会了树链剖分,\(^O^)/)

以BZOJ1036为例,题目中要求树上的区间和,最值询问.
若不是在树上的话,很容易想到用线段树来来实现.

树链剖分实质

而树链剖分实际上也就是将树结构剖分成一条一条的链结构,实际上就是区间,以便于数据结构的操作.
就题目而言,若将树剖分成链,那么就很容易再在线段树的基础上维护.

如何剖分?

显然链的条数越少越好,那么对于一个点而言,他只能和其子节点中选择一个分成一条链,其余的另外成链.要使得条数尽可能少,就应当选择节点个数最多的那个子节点,称之为重儿子.其他的轻儿子就另外成链.将节点与其重儿子所形成的边称为重边,其他边称之为轻边.

重链的性质

将全部由重边形成的链称之为重链,可以发现重链和轻边的条数均为log级别的.
应为对于一个点而言,若其不为父节点的重儿子,那么其子树结点和必定小于等于总结点数的1/2,那么一直出现新的重链只能有log级别次.
且轻边的条数也是log级别次,因为每两条重链间夹着一条轻边.

两点间路径的遍历

很多时候用到树链剖分是在树上操作,那么就可能会涉及到两点间距离操作.
以两点x,y间求区间点权和为例:
1.讨论x,y是否在同一条重链上:
若在,直接用数据结构求[x,y]区间和.
若不在,将所在链顶端较深的向上移动,并记录下该点到所在链顶端的区间和.

代码实现

1.找重边

dfs求出子树结点数,取节点数最大的作为重儿子,得到重儿子之后便知道结点的重边.

int fa[maxn],sz[maxn],son[maxn];//fa 父节点数组 sz 子树结点数 son 结点重儿子void dfs1(int u,int p)//u 当前结点 p 父节点{    sz[u]=1;fa[u]=p;son[u]=0;    for(int i=fir[u];i;i=nxt[i]) if(to[i]!=p) {        int v=to[i];        dfs1(v,u);        sz[u]+=sz[v];        if(sz[v]>sz[son[u]]) son[u]=v;    }}

2.成重链

已经求出了重边,现在要将其连起来.既然要连起来,那么重链上的点肯定要连续,那么尝试将同一重链上的点连续存放,形成多个连续区间,那么就将多条重链连起来了.

int top[maxn],dep[maxn],rnk[maxn],idx[maxn],id;//top 结点所在重链的顶端 dep 结点深度 rnk 新标号映射旧标号 idx 旧标号映射新标号void dfs2(int u,int t,int d)//u 当前结点 t 结点所在重链顶端 d 结点深度{    top[u]=t;dep[u]=d;rnk[idx[u]=++id]=u;    if(son[u]) dfs2(son[u],t,d+1);//先dfs重儿子,才能保证同重链上的点连续存放    for(int i=fir[u];i;i=nxt[i])        if(to[i]!=fa[u]&&to[i]!=son[u]) dfs2(to[i],to[i],d+1);}

3.两点间路径(以路径点权和为例)

int solve(int x,int y){    int ret=0;    while(top[x]!=top[y]) {        if(dep[top[x]]<dep[top[y]]) swap(x,y);        ret+=query(1,1,N,idx[top[x]],idx[x],k);        x=fa[top[x]];    }    if(dep[x]>dep[y]) swap(x,y);    ret+=query(1,1,N,idx[x],idx[y],k);    return ret;}

4.求lca

int lca(int x,int y){    while(top[x]!=top[y])        if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];        else y=fa[top[y]];    return dep[x]<dep[y]?x:y;}

相关题目:

1.BZOJ 1036 题目 题解
2.BZOJ 3637 题目 题解
3.POJ 3237 题目 题解

0 0
原创粉丝点击