树链剖分
树链剖分
简介
树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。
具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息;
重边与轻边,重链与轻链
在将一棵树分成若干条链时,我们最好不要盲目的分;
有一种有效的分割方法就是,分成重链与轻链;
重链:连续的重边组成一条链;
轻链:连续的轻边组成一条链;
那么怎么判断重边和轻边呢?
我们定义
重子节点 :表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点;
轻子节点 :表示剩余的所有子结点;
那么重边和轻边就是
重边:重子节点与它父节点相连的一条边;
轻边:轻子节点与它父节点相连的一条边;
如图
代码实现
首先我们给出一些;
重要的数组
$f[x]$ 表示 $x$ 节点的父节点;
$son[x]$ 表示 $x$ 节点的重子节点;
$dep[x]$ 表示 $x$ 节点的深度;
$size[x]$ 表示 $x$ 节点的子树的节点个数;
$top[x]$ 表示 $x$ 节点所在这条链上的顶部节点;
$id[x]$ 表示 $x$ 节点的$DFN$序,也就是在线段树中的编号;
$aa[x]$ 表示 $x$ $DFN$序的节点权值;
那么
剖分代码
inline void dfs(ll x,ll fa)//找重子节点 { size[x]=1; f[x]=fa;//记录父节点 for(re ll i=head[x];i;i=e[i].stb) { ll xx=e[i].to; if(xx==fa)//不能遍历到父节点 continue; dep[xx]=dep[x]+1;//统计深度 dfs(xx,x); size[x]+=size[xx];//统计子树节点数 if(!son[x]||size[xx]>size[son[x]]) son[x]=xx;//找重子节点,也就是子树节点数最多的子节点 } } ll tot=0;//统计在线段树中的编号 inline void DFS(ll x,ll t)//t 表示这条链的顶部 { top[x]=t;//记录 id[x]=++tot;//记录在线段树中的编号 aa[tot]=w[x];//记录在线段树中的权值 if(!son[x])//如果没有重子节点 return;//返回 DFS(son[x],t);//先遍历重子节点 for(re ll i=head[x];i;i=e[i].stb) { ll xx=e[i].to; if(xx==f[x]||xx==son[x])//遍历轻子节点 continue; DFS(xx,xx);//每个开始的轻子节点的链顶就是自己 } }
调用代码
dfs(1,0); DFS(1,1);//1 所在链的顶部就是自己
求LCA
inline ll LCA(ll x,ll y) { while(top[x]!=top[y])//如果不在同一条重链 { if(dep[top[x]]>dep[top[y]])//将深度大往上跳,使得最后 x, y在同一条链上 x=f[top[x]]; else y=f[top[y]]; } if(dep[x]<dep[y]) // x,y 深度较小的就是 LCA return x; else return y; }//读者可以按照上面的图模拟一下
线段树维护信息
不同的是求树上两个点之间的信息;
修改就直接用线段树模板;
建树
inline void build(ll p,ll l,ll r)//建树 {//果然没改啥吧 a[p].l=l; a[p].r=r; if(l==r) { a[p].v=aa[l];//熟悉的赋值操作 return; } ll mid=(l+r)>>1; build(L(p),l,mid); build(R(p),mid+1,r); doit(p);//熟悉的传递 }
调用
build(1,1,n);
单点修改
inline void change(ll p,ll x,ll y)//单点修改 { if(a[p].l==a[p].r) { a[p].v=y; return; } ll mid=(a[p].l+a[p].r)>>1; if(x<=mid) change(L(p),x,y); else change(R(p),x,y); doit(p); }
调用
change(1,id[x],y);//改下编号就好了
树上两点间求和
收先需要写一个线段树区间求和来维护;
inline ll findsum(ll p,ll l,ll r)//找区间sum { if(l<=a[p].l&&a[p].r<=r) return a[p].v; ll sum=0; ll mid=(a[p].l+a[p].r)>>1; if(l<=mid) sum+=findsum(L(p),l,r); if(r>mid) sum+=findsum(R(p),l,r); return sum; }
然后就让两个点不停加上这个点与链顶的距离,在往上跳到上面一条链;
最后使得两个点在同一条链上时,加上两个点之间的区间和即可;
inline ll qsum(ll x,ll y) { ll sum=0; while(top[x]!=top[y])//我们需要是 x 节点跳到与 xx 节点在同一条链上 { if(dep[top[x]]<dep[top[y]])//深度大的往上跳 swap(x,y); sum+=findsum(1,id[top[x]],id[x]);//统计 x 到链顶的 sum x=f[top[x]];// 跳到下一个区间,也就是在 top[x] 上面的链 } if(dep[x]<dep[y]) swap(x,y); sum+=findsum(1,id[y],id[x]);//在统计下 x 到 xx 的区间sum //此时 x 与 xx 是在同一条链上 return sum; }
树上两点间求最大边权
和两点间求和差不多思路,那么还是放下代码吧;
inline ll findmax(ll p,ll l,ll r)//区间最大值 { if(l<=a[p].l&&a[p].r<=r) return a[p].mx; ll sum=-(1<<30); ll mid=(a[p].l+a[p].r)>>1; if(l<=mid) sum=max(sum,findmax(L(p),l,r)); if(r>mid) sum=max(sum,findmax(R(p),l,r)); return sum; }
inline ll qmax(ll x,ll y) { ll sum=-(1<<30); while(top[x]!=top[y])//我们需要是 x 节点跳到与 xx 节点在同一条链上 { if(dep[top[x]]<dep[top[y]])//深度大的往上跳 swap(x,y); sum=max(sum,findmax(1,id[top[x]],id[x]));//统计 x 到链顶的 最大值 x=f[top[x]];// 跳到下一个区间,也就是在 top[x] 上面的链 } if(dep[x]<dep[y]) swap(x,y); sum=max(sum,findmax(1,id[y],id[x]));//在统计下 x 到 xx 的区间最大值 //此时 x 与 xx 是在同一条链上 return sum; }
读者可以结合上面图的进行理解;