前言
对于树的形态不改变但是需要对路径进行修改操作的,数量剖分就是一个很好的选择,也就是把树弄成链状的然后用数据结构去维护它。
哎哎哎,我突然发现树剖出来的也是DFS序,只是一种特殊的DFS序而已,因此可以维护子树啊!
原理
为了把树上的东西变成线性的,我们采取的办法就是把树弄成若干条链,并且放在一个序列中,这样就可以被一般的数据结构维护了,然后链一段一段的选取修改或者查询,这就是数量剖分的含义了。
为了得到比较好的时间复杂度,我们希望对于任何路径都可以表示成尽量少的链,这里说一个最常用的方法,就是所谓的“轻重链剖分”。
引入几个定义:
重儿子:如果j是i的子树中节点数量最多的,则称j就是i的重儿子。
轻儿子:除重儿子以外的其它子节点。
重边:点i与重儿子的连边。
轻边:点i与其轻儿子的连边。
重链:由重边连成的路径。(有时候单独的结点也算)。
轻链:轻边。
就根据这写定义拆就好了。
性质
性质1:如果(i,j)为轻边,则sz[j]*2 < sz[i];
性质2:从根到某一点j的路径上轻链、重链的个数都不大于log2n。
(根据所有走的都是轻边,然后重边子树必须倍增这一点来证明)
实现思路
首先我们要维护:子树大小、深度(为了之后的查询)、重儿子、父亲、树链的位置,重链的顶端结点。
我们进行两次DFS
第一次:求出sz,dep,son,fa
第二次:求出tr,top(借助时间戳)
最后就是找路径:不停通过重链往上爬(爬重链顶端结点深的那个),直到两个结点所在重链的顶端结点相同,当然还没完,要把两个结点在同一条重链时的路径也算进去。
注意
没什么特别的,注意有的时候注意一下LCA那个点(比如修改边权的时候)
关于题目
其实根本就没什么难的,真的难的其实是用什么数据结构维护,也就是一种强行把线性的弄到树上去的一种操作而已。。。
核心代码
int dfn,sz[maxn],dep[maxn],son[maxn],fa[maxn],tr[maxn],top[maxn];
void DFS1(int i,int f,int d)
{
sz[i]=1,dep[i]=d,fa[i]=f,son[i]=0;
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
if(j==f)continue;
DFS1(j,i,d+1);
if(sz[son[i]]<sz[j])son[i]=j;
sz[i]+=sz[j];
}
}
void DFS2(int i,int tp)
{
top[i]=tp,tr[i]=++dfn;
if(son[i])DFS2(son[i],tp);
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
if(j==fa[i] || j==son[i])continue;
DFS2(j,j);
}
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
至于那个LCA嘛,可以和修改操作放在一起写,没必要写把LCA算出来。