Link-Cut-Tree之前经常听大神说,然而发现它是动态树的时候,我才知道是我太弱了= =
那么LCT好处都有啥?
1、支持动态加边删边(维护森林)
2、可以维护区间信息
3、所有操作可在线完成且均摊效率log(n)
LCT的结构:将整个树的边分为轻边和重边(Preferred edge) ,与树链剖分类似(没学过请自觉面壁)
其中重边是进行操作的边,每个点只能有一个重边连接的儿子(Preferred child),于是可以证明每个点在且仅在一个重链上(没有边的点本身算作重链)
我们要做的是将重链放到splay中维护(没学过请自觉面壁),同时让splay树的根记录原树重链顶部节点的(轻边所连的)父亲,这时原树已经成为了一个概念,实际保存的是用father指针维护的各种splay,值得指出的是,splay的根不一定是真正的重链的顶点,实际的顶点应当在splay的最左端,因为对于splay的每个节点,其左儿子为它原树上的祖先,右儿子为它原树重链上的后代。
这样我们要维护的数组有:f[maxn] ch[maxn][2],其中f对于每棵splay的根节点表示这个重链顶端的父亲,其余用于维护splay结构 ;ch维护splay上的左右儿子,并不表示原树上的边
根据如上定义可以给出判断一个点是否为splay 的根的方法
bool isroot(int x)
{
return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;
}
通常来说,LCT只需要拿来判断图的联通,即森林中的两点是否在同一棵树上(例题:bzoj2049),有些题目在link-cut的同时也要求维护权值,只要加一个函数maintain一下就可以支持。需要maintain的权值(sum,max等)信息随题目而异,但大体需要从splay上的两个儿子更新自身的值。以下代码中含maintain如只需判断连通可删去。
下面是LCT的基本操作
1、splay(t) 及 rotate:使t到达splay的根,与正常splay不同的是要通过儿子找到父亲再旋转,代码如下
void rotate(int k)//lift k to a higher level
{
int t=f[k];
bool l;
l=(ch[t][0]!=k);
bool r=l^1;
if(!isroot(t))
ch[f[t]][ch[f[t]][1]==t]=k;
f[k]=f[t];
f[t]=k;
f[ch[k][r]]=t;
ch[t][l]=ch[k][r];
ch[k][r]=t;
maintain(t);
maintain(k);
}
void splay(int t)
{
stack<int> stk;
int i;
for(i=t;!isroot(i);i=f[i])
stk.push(i);
stk.push(i);
while(!stk.empty())
{
pushdown(stk.top());
stk.pop();
}
while(!isroot(t))
{
int fa=f[t];
if(!isroot(fa))
{
int ffa=f[fa];
bool d1=ch[ffa][1]==fa;
bool d2=ch[fa][1]==t;
if(d1^d2) rotate(t);
else rotate(fa);
}
rotate(t);
}
}
pushdown的作用在第四条体现
2、access(t):使根节点到t的路径变为一个重链,且保证t所在splay上只含root-t路径上的点
具体方法:首先将t splay到所在splay_tree的根,然后断开它与其splay上右儿子,即原树上t的后代,然后找到该链的父亲将其重儿子设置为t,重复操作直到到达原树的根所在splay的根,也就是没有父亲的点
void access(int t)
{
int tmp=0;
while(t)
{
splay(t);
ch[t][1]=tmp;
tmp=t;
maintain(t);
t=f[t];
}
}
3、root(t) 找到t所在原树的根,用于判断两点之间是否连通
代码1:真正找到所在原树的根,这需要将t与root相连,然后确保t在splay根节点后向左走
int root(int t)
{
access(t);
splay(t);
while(ch[t][0]) t=ch[t][0];
return t;
}
代码2:找到
原树的根所在splay的根,同样可以判断连通,而且代码更加简洁,但不够严谨
int root(int t)
{
while(f[t]) t=f[t];
return t;
}
4、moveroot(t) :将t所在原树的树根设为t
void moveroot(int t)
{
access(t);
splay(t);
rev[t]^=1;
}
rev是反转标记,这段代码的用意是将root-t路径分离出来,然后对条路径上的儿子-父亲关系进行反转,体现在splay上就是区间翻转的操作 注意
换根操作只对root-t路径产生影响
pushdown函数就是为了支持rev的翻转操作
void pushdown(int t)
{
if(rev[t])
{
rev[ch[t][0]]^=1;
rev[ch[t][1]]^=1;
rev[t]=0;
swap(ch[t][0],ch[t][1]);
}
}
5、split(x,y)
将x到y的路径分离出来放到splay中且保证y为splay的根,若需要维护路径信息,y上的值即为答案
void split(int x,int y)
{
moveroot(x);
access(y);
splay(y);
}
6、cut(x,y)
void try_cut(int x,int y)
{
split(x,y);
if(ch[y][0]==x&&!ch[x][0]&&!ch[x][1])//判断节点是否有边
{
ch[y][0]=f[x]=0;
maintain(y);
}
}
7、link(x,y)
void link(int x,int y)
{
moveroot(x);//保证f[x]为空
f[x]=y;
}
附:bzoj2049代码
/**************************************************************
Problem: 2049
User: Leo_h
Language: C++
Result: Accepted
Time:4800 ms
Memory:992 kb
****************************************************************/
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
#define maxn 10005
int n,m;
int f[maxn];
int ch[maxn][2];
int rev[maxn];
bool isroot(int x)
{
return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;
}
void rotate(int k)//lift k to a higher level
{
int t=f[k];
bool l;
l=(ch[t][0]!=k);
bool r=l^1;
if(!isroot(t))
ch[f[t]][ch[f[t]][1]==t]=k;
f[k]=f[t];
f[t]=k;
f[ch[k][r]]=t;
ch[t][l]=ch[k][r];
ch[k][r]=t;
}
void pushdown(int t)
{
if(rev[t])
{
rev[ch[t][0]]^=1;
rev[ch[t][1]]^=1;
rev[t]=0;
swap(ch[t][0],ch[t][1]);
}
}
void splay(int t)
{
stack<int> stk;
int i;
for(i=t;!isroot(i);i=f[i])
stk.push(i);
stk.push(i);//you modified this
while(!stk.empty())
{
pushdown(stk.top());
stk.pop();
}
while(!isroot(t))
{
int fa=f[t];
if(!isroot(fa))
{
int ffa=f[fa];
bool d1=ch[ffa][1]==fa;
bool d2=ch[fa][1]==t;
if(d1^d2) rotate(t);
else rotate(fa);
}
rotate(t);
}
}
void access(int t)
{
int tmp=0;
while(t)
{
splay(t);
ch[t][1]=tmp;
tmp=t;
t=f[t];
}
}
void moveroot(int t)
{
access(t);
splay(t);
rev[t]^=1;
}
int root(int t)
{
while(f[t]) t=f[t];
return t;
}
void link(int x,int y)
{
moveroot(x);
f[x]=y;
}
void split(int x,int y)
{
moveroot(x);
access(y);
splay(y);
}
void cut(int x,int y)
{
split(x,y);
ch[y][0]=f[x]=0;
}
int main()
{
scanf("%d%d",&n,&m);
char str[10];
int f1,f2;
for(int i=1;i<=m;i++)
{
scanf("%s%d%d",str,&f1,&f2);
switch(*str)
{
case 'C':
link(f1,f2);
break;
case 'D':
cut(f1,f2);
break;
case 'Q':
if(root(f1)==root(f2)) printf("Yes\n");
else printf("No\n");
break;
}
}
}