以前做LCT基本上都是拿来当模板用,于是总是会出各种错误,乱搞半天才过...随着玄妙的LCT题的增多,总算渐渐理解了LCT的本质,就记一下好了
定义一下:fa[x]是splay中的父亲,splay的根的fa是另一颗splay中的一个点,即special father
一定要牢记:LCT的splay是按深度为关键字排序,每个splay维护的是一条奇怪的链.
access操作:从x跳到根,将路上散乱的splay合并起来
void access(int x){
int y=0;
for(;x;y=x,x=fa[x]){
splay(x);
ch[x][1]=y;//将x这个链加入生成的新链中,断掉x在子树中其他的儿子
upd(x);
}
}
可以发现,access操作会形成一个只包含x到根所有点的splay.所以如果splay(x),则ch[x][1]为空.
makeroot操作:先access,然后就造了一个x到根的链。由于LCT是按照深度排序,所以做反转操作后x就变成了深度最小,即根.
void makeroot(int x){
access(x),splay(x);
tg[x]^=1,pd(x);
}
link操作:比如link(x,y),如果想要把x接到y上,就要makeroot(x),fa[x]=y;
void link(int x,int y){
makeroot(x),fa[x]=y;
}
如果不makeroot(x),拿access(x),splay(x);来替代的话,那么实际上连接的其实是x所在splay中最左点和y.
这拿深度排序很好解释...
cut操作:cut(x,y),先将x设为根,可以有如下实现.
void cut(int x,int y){
makeroot(x),access(y),splay(y);
fa[x]=0,ch[y][0]=0;
upd(y);
}
由于x,y有边相连,access(y)后根据前面的理论,这个splay里就应该只剩x,y了,且x是y的左儿子.
这时直接断就好了.
有些不换根的题目会让你无法自由地用access,这时cut(x,y)可以这样实现:
假设y=fa[x],那么splay(x)后将x的左儿子接到x的fa上,再将x的左儿子设为0.
即:splay(x),fa[ch[x][0]]=fa[x],ch[x][0]=0;
这样为什么对呢?按照深度排序的理论,断(x,y)就是断x的左子树.这样就很好解释了.
利用access的思想,还可以做什么将x到根赋同一个值的操作:access是将这些点拿出来造一个新的Splay,于是就可以每个splay记个赋值标记.这样通过access的均摊O(logn)的思想就可以降下复杂度.
LCT还可以维护子树信息.可以将x的儿子分为实儿子和虚儿子.实儿子就是splay中的儿子,虚儿子就是原树中的其他边,连向另一些splay.这时拿个什么数据结构来维护这些虚儿子的值,然后access时有断实儿子连虚儿子,link有连虚儿子操作,update要合并虚儿子信息,这些地方改一下即可.
顺便记一下bzoj4530(动态维护子树大小,支持换根)的代码...
#include<bits/stdc++.h>
#define maxn 100100
using namespace std;
int fa[maxn],tg[maxn],n,m,f[maxn],s[maxn];
int ch[maxn][2],sz[maxn],val[maxn];
void pd(int o){
if(tg[o]){
swap(ch[o][0],ch[o][1]);
if(ch[o][0])tg[ch[o][0]]^=1;
if(ch[o][1])tg[ch[o][1]]^=1;
tg[o]=0;
}
}
void upd(int o){
sz[o]=val[o];
if(ch[o][0])sz[o]+=sz[ch[o][0]];//,len[o]+=len[ch[o][0]];
if(ch[o][1])sz[o]+=sz[ch[o][1]];//,len[o]+=len[ch[o][1]];
}
bool isroot(int x){
if(!x||!fa[x])return true;
return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void rotate(int p){
int q=fa[p],y=fa[q],k=(ch[q][1]==p);
if(!isroot(q))ch[y][ch[y][1]==q]=p;
fa[ch[q][k]=ch[p][k^1]]=q;
fa[ch[p][k^1]=q]=p,fa[p]=y;
upd(q);
}
void splay(int x){
int y;
while(!isroot(x)){
pd(fa[y=fa[x]]),pd(y),pd(x);
if(!isroot(y)){
if((ch[fa[y]][1]==y)^(ch[y][1]==x))rotate(x);
else rotate(y);
}
rotate(x);
}
upd(x),pd(x);
}
void access(int x){
int y=0,fsz=0;
for(;x;y=x,x=fa[x]){
splay(x);
if(ch[x][1])val[x]+=sz[ch[x][1]];
if(y)val[x]-=sz[y];
ch[x][1]=y;
fsz=sz[x],upd(x);
}
}
void rever(int x){
access(x),splay(x),tg[x]^=1,pd(x);
}
void clear(int x){
sz[x]=val[x]=1;
fa[x]=ch[x][0]=ch[x][1]=tg[x]=0;
}
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
char op[3];
long long ans=0;
int main(){
freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)clear(i),f[i]=i,s[i]=1;
for(int i=1,x,y;i<=m;++i){
scanf("%s%d%d",op,&x,&y);
if(op[0]=='A'){
// access(x),splay(x);
// access(y),splay(y);
rever(x),rever(y);
sz[x]+=sz[y],val[x]+=sz[y],fa[y]=x;
// x=find(x),y=find(y),f[x]=y,s[y]+=s[x];
} else {
rever(x),access(y),splay(y);
// if(sz[y]!=s[find(y)])exit(-1);
// printf("[%d,%d]",sz[y],val[y]);
printf("%lld\n",1ll*(sz[y]-val[y])*val[y]);
}
}
}
PS:一些注意事项
splay后要pushdown&&update
找儿子,找根什么的要pushdown
rotate时update只需一次
有换根操作就不要再想什么玄妙地断父亲的边