前言
其实我早就知道Link Cut Tree 是什么东东,可是懒癌晚期不想仔细学。
简单点说就是动态的树链剖分,因为我们链剖用的是线段树,死在那里。
既然要用动态,那么肯定要用平衡树来维护啦,我们选择splay。
如果不会splay的还是别看了,splay学习小记
一些东西
其实这个东西杨哲的集训队作业也是有的,大家可以先去看一看。
我们把之前访问到的点称作偏爱点,从根到偏爱点的路径叫做偏爱路径。偏爱路径上的边叫做偏爱边。每个点向它的儿子只会连一条偏爱边。
我们用splay维护所有的偏爱路径,这样就形成了一片森林。
splay中点x保证左节点的深度小于x,右节点的深度大于x。
然后,对于每个splay中的根,它在原树中往上第一条不是偏爱边所连向的点叫做它的Path Parent,连接这两个点的边叫做Path Parent边。
这样就形成了一棵LinkCutTree。
盗图狗路过
功能
要实现LinkCutTree的功能,我们就必须让那些点在同一棵splay上。
也就是说,我们需要用access(x)来把原树的根到x点的路径上所有的边变成偏爱边。
void access(int x){
int y=0;
while(x){
splay(x,0);
f[t[x][1]]=0,pfa[t[x][1]]=x;
t[x][1]=y,f[y]=x;
pfa[y]=0;
update(x);
y=x,x=pfa[x];
}
}
其实就是沿着path parent边往上跳。
为什么要把右边断掉?
因为根到x中的点的深度都比x小,比x大的就不在偏爱路径上了。
这个操作是LinkCutTree的核心。
不过注意,执行splay操作的时候可能会改变pfa(因为这一棵splay的根改变了),所以旋转的时候要注意。
那些要维护的值就和普通splay一样维护就可以了。
接下来要讲的就是关于动态树的动态操作
换根
void makeroot(int x) {
access(x);splay(x,0);rev[x]^=1;
}
这里就是我们要选用splay的原因。
因为我们有翻转操作。
为什么呢?
因为splay是按深度来建的,把x换到根之后,1~x的深度全部翻转了。
连边
void link(int x,int y) {
makeroot(x);p[x]=y;
}
很显然吧。
删边
void cut(int x,int y) {
makeroot(x);access(y);splay(y,0);
t[y][0]=0;f[x]=p[x]=0;
updata(y);
}
这个比较巧妙。
先把x换到根,然后把x到y的路径改成偏爱路径。
这样x和y就在同一棵splay上了。且x比y的深度小。
如果把y splay上去,x就会在y的左边。
断掉就好了。
求x到y的路径
makeroot(x);access(y);splay(y,0);
这样y点的值就是x到y的路径上的值。
原因一样。
然后这些就够用了吧。
哦,还有一点。
如何用LinkCutTree维护边权?
一个直观的想法是把每个点的点权设为它父边的边权。
但是这样很明显会错。
因为在access操作中树的形态会改变。
那么怎么办呢?
某神犇:把每条边看成点,维护点权就好了
肿么就辣么机智勒。