总结-树链剖分
(今天学会了树链剖分,\(^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;
}