是谁给我的勇气,让我在一道树链剖分都没写的情况下就来写博客?
当然是xxx啦
进入正题。
按照我的理解,我们可以用dfs序处理很多问题,各种差分各种求和都可以,但是当我们遇到链加子树求和这种情况时,你会发现我们好像没有了时间复杂度优秀的手段。
于是大佬们想出了树链剖分。
定义一下:
重边是一个结点的儿子中size(子树大小)最大的那个点与自己相连的边。
重儿子就是一个节点最疼爱的儿子用重边连接起来的儿子。
重链自然就是一串重边连起来的东西。
轻边就是不是重边的边(好扯的定义)。没有轻链这一说ovo。
树链剖分的意义在于,它通过将原树切成一条条的不相交的重链来方便维护。比如说用线段树什么的。重链和重链间用轻边连接(废话除了重边就只有轻边)。
于是在询问的时候,可以通过查询一条重链里的信息,然后遇到轻边就暴力跳过去就行。(大佬眼里的暴力qwq)
我把树链剖分的实现称之为DD,即Double Dfs。
第一遍dfs:算出所有点的父亲fa,子树大小size,深度dep,以及所有儿子中结点最多的那个儿子son,都记录下来。
第二遍dfs:算出所有点所在的重链的顶端top(利用第一遍算出来的son和size),以及所有点的dfs序。
这里要注意在第二遍算dfs序的时候,要优先走重儿子。为什么呢!我认为这是最重要的:因为先走重儿子你的重链上点的dfs序才是连续的,这样才能用线段树维护,要不然序号不一样线段树也要泪奔。代码如下:
void dfs(int x){
size[x]=1;
for (int i=first[x],y;i;i=nxt[i])
if ((y=to[i])!=fa[x]){
fa[y]=x;
dep[y]=dep[x]+1;
dfs(y);
size[x]+=size[y];
if (size[y]>size[son[x]]) son[x]=y;//确定重儿子
}
}
void dfs(int x,int Anc){//当前重链的顶端为Anc
id[x]=++tot;
top[x]=Anc;
if (son[x]) dfs(son[x],Anc);//优先处理重儿子
for (int i=first[x],y;i;i=nxt[i])
if ((y=to[i])!=fa[x]&&y!=son[x]) dfs(y,y);//如果是轻边,那就意味着新开了一条重链
}
那树链剖分到底怎么查询信息呢QAQ?
由于之前我们已经令一条重链上的结点序号连续,那么剩下的就是线段树了。
这里的查询方式很简单:
1.给出要查询的链的两个端点x,y,注意这条链可能横跨多条重链。
2.令a,b分别为x,y的top(重链顶端)。
3.把深的先查询,然后往上跳(像LCA那样)。
4.判断跳完后的新top a,b是否相等。如果还不相等,就回到第二步继续跳,反之进入第五步。
5.还要查一次,也就是最后的一条链。
换句话说,两个端点同时出发,你跳一下,我跳一下,最后撞到一起了就停下。
事实上,树链剖分求LCA是非常简单的。只要两个点跳到top相等了,深度较浅(也就是较高的那个)就是LCA。
代码实现如下:
int calc(int x,int y){
int a=top[x],b=top[y];//ab是top,xy是端点
int res=0;//这是你要查询的信息变量,,因题而异,这里默认为求链长
while (a!=b){
if (dep[a]>dep[b]) swap(a,b),swap(x,y);//保证b是深的那个。
Ql=id[b],Qr=id[y];//该重链的两个端点
res+=Query(1,1,n);//因为dfs序连续,所以直接查询。QL和QR是全局变量
y=fa[b],b=top[y];//将深的那个top变成父亲(上面那条重链的尾巴),而原来的b又变成了上面那条重链的top。
}
Ql=id[x],Qr=id[y];//最后处理(还剩一小块)
if (Ql>Qr) swap(Ql,Qr);
res+=Query(1,1,n);
return res;
}
树链剖分和线段树,树状数组,splay等数据结构是如影随形的,像我这种线段树都不会写的菜菜可以自闭了QAQ