您的位置:首页 > 其它

浅谈Link-Cut-Tree([林可砍树]LCT动态树)附例题 Hdu4010

2017-07-19 21:01 525 查看
其实一直对LCT很好奇,到底是个什么数据结构可以搞这么多东西,还可以动态地维护,支持加边,合并树,查询等操作,比链剖要强大很多

学过链剖的同学应该可以很快地理解LCT的思路,LCT的实现需要构造辅助树,我们通常选用splay,因为splay方便的序列操作使得LCT的实现显得得心应手,尤其是在换根的时候,只需要splay树上打上一个标记即可

那么LCT到底可以干什么呢?

我们来浅显地介绍这样一个神奇的数据结构

1.支持树链操作,包括树链的边权的加减,查询最大值最小值等,且复杂度均摊是logn的。

看到这里的同学可能会说,这些链剖也可以搞啊,而且链剖那么好写,10分钟写完调完

然而LCT好像并没有那么简单

2.LCT支持加边操作,这个链剖就没法搞了吧,我要把两棵树合到一起,然后再查询跨越两棵树的链的信息,显然这时候用链剖就是大暴力

3.LCT支持换根操作,也就是把一个点和当前的根置换,其实开始我一直理解错了这个换根,以为就只是换一个那两个节点,其实不是的,我们看一张图



换根大概是这么个操作,树都还是连在一起的,这个链剖没法搞吧

3.LCT还支持很多很多自己的操作,比如JOIN,CUT等,下面会详细讲解

….

首先,介绍一下一些LCT稍微学术化的概念

如果一个点被访问了,那么指刚刚对这个点执行了ACCESS操作,什么是ACCESS操作,现在可以不必管,大概是把一个点到根拉成一条链吧,想像链剖分轻重链,如果这个点被访问了,那么他就是他father的Preferred Child,并且这个点到father的边被称为Preferred Edge,Preferred Edge连成的边自然就叫Preferred Path啦,大家可以参考一下刘汝佳的图片,这里我随便画个图,大概表示一下这个ACCESS(x)的操作



然后这棵树被划分了,也就是如图所示的Preferred_Path和普通的树边, Preferred_Path连起来的所有点是被一棵splay树维护的,而其他所有独立的点(比如上图的左边两点,右边的图中,都分别是一棵splay树,也就是只有一个节点splay树)

好吧大家感性认知一下就好,之后还要理性理解的,放心

ACCESS操作

首先如果我们都知道splay的各种操作,现在,就可以来搞LCT的ACCESS操作了,先附代码

inline void access(int loc){
for(register int i=0;loc;i=loc,loc=tree[loc].fa){
//首先,对于一棵平衡树,如果不是根节点,那么father是正常指的,但是如果是根节点,指的应该是该平衡树的根节点的father,见下图,一直到loc=0,也就是根的位置,loc一直往fa上找
splay(loc);//先吧loc splay到loc所在splay树的根,然后找fa,就可以很快地直接跳到下一棵splay树上,是非常高效的
tree[loc].child[1]=i;//i的位置在loc的下面,按照深度,深度比loc大,成为loc的右儿子
pushup(loc);//由于旋转了loc,所以更新信息
}
}




显然如果现在我对z,y,x这棵平衡树,进行splay(y)操作,y变成了这棵平衡树的根节点,那么以深度来排平衡树的key值的话,z点是y的左儿子,x是y的右儿子,显然,z和x的father都是y,而y的father,则应该是w,这就是我们构造了辅助树(我们通常选用splay树作为这个辅助树),一定要分清father和son的关系

这个操作可以把一个指定的点到树拉通,但显然需要熟悉地写出splay树,那么下面我们来写splay树(如果很熟练的朋友可以直接跳这部分啦)

inline bool isroot(int x){
return tree[tree[x].fa].child[0]!=x&&tree[tree[x].fa].child[1]!=x;
}//判断x节点是不是其所在splay的根,显然根据之前所讲splay的father连接关系,如果其father的左子和右子都不是他,那么显然他是根,下面有图示例子
inline void add(int loc,int val){
tree[loc].val+=val;tree[loc].maxn+=val;tree[loc].add+=val;
}
//进行add操作,对于这棵splay树的根节点进行val的累加,maxn的累加和标记的累加,注意改变这棵splay树的结构的时候一定要记得pushdown
inline void pushup(int loc){
tree[loc].maxn=max(tree[tree[loc].child[0]].maxn,tree[tree[loc].child[1]].maxn);
tree[loc].maxn=max(tree[loc].maxn,tree[loc].val);
}
//pushup看起来是非常清晰得,这里不再过多解释,这里只以维护最大值为例
inline void pushdown(int loc){
if(tree[loc].rev){
tree[loc].rev^=1;tree[tree[loc].child[0]].rev^=1;tree[tree[loc].child[1]].rev^=1;
swap(tree[loc].child[0],tree[loc].child[1]);
}//splay写区间翻转就是好,区间翻转就是换根,之后会讲解为什么就是换根
if(tree[loc].add){
if(tree[loc].child[0]) add(tree[loc].child[0],tree[loc].add);
if(tree[loc].child[1]) add(tree[loc].child[1],tree[loc].add);//不要忘了左右子树的判空
tree[loc].add=0;
}
}
//pushdown操作也是很清晰的操作,会平衡树和线段树等数据结构的朋友应该可以迅速看懂
inline void Pushdown(int loc){
if(!isroot(loc)) Pushdown(tree[loc].fa);
pushdown(loc);//显然一直找到该树的根,然后下传根的标记,因为我们之前打的所有标记都是在root上
}
inline void rotate(int x){
int y=tree[x].fa,z=tree[y].fa,l,r;
if(tree[y].child[0]==x)l=0;else l=1;r=l^1;
if(!isroot(y)){
if(tree[z].child[0]==y) tree[z].child[0]=x;
else tree[z].child[1]=x;
}
tree[x].fa=z;tree[y].fa=x;tree[y].child[l]=tree[x].child[r];
tree[tree[x].child[r]].fa=y;tree[x].child[r]=y;
pushup(y);pushup(x);
}
inline void splay(int x){
Pushdown(x);
while(!isroot(x)){
int y=tree[x].fa,z=tree[y].fa;
if(!isroot(y)){
if(tree[y].child[0]==loc^tree[z].child[0]) rotate(loc);
else rotate(y);
}
rotate(loc);
}
}//splay及其rotate操作,Orz hzwer,不多说


关于isroot函数



显然如果现在y是其所在splay树的根节点,那么他的fa一定是连向另一颗splay树的,显然对于另一个splay树的lson和rson都不会连回这棵树,否则就是一棵splay树了

接下来就是几个比较有趣的函数了

比如findroot函数,这个函数是要求loc所在的树(并非辅助树)的根

inline int findroot(int loc){
access(loc);splay(loc);
//先将loc所在的到根的链拉成一条,然后把loc旋转到根,显然我们的splay树是用深度来排的序,那么一直往左找,直到不能往下找了,就找到根了
while(tree[loc].child[0]) loc=tree[loc].child[0];
return loc;
}


reverse函数

inline void reverse(int loc){
access(loc);splay(loc);tree[loc].rev^=1;
}


reverse也就是换根操作,也就是splay的区间翻转操作

inline int link(int x,int y){
if(findroot(x)==findroot(y)||x==y) return -1;
reverse(x);tree[x].fa=y;
return 0;
}


link函数

inline int link(int x,int y){
if(findroot(x)==findroot(y)||x==y) return -1;
reverse(x);tree[x].fa=y;
return 0;
}


显然,这个link函数是把两棵splay森林,也就是LCT连起来,如果x的root和y的root(注意是真正树结构的root,不是splay树的root)或者是x==y,那么说明不能连接,否则直接把x所在的树换根为x,然后再把x的fa设置成y即可[这里有一点值得注意,findroot的时候其实已经access并且splay过x了,所以直接reverse]

cut操作

inline int cut(int x,int y){
if(findroot(x)!=findroot(y)||x==y) return -1;//也是保险起见,判一下
reverse(x);access(y);splay(y);//同样的操作
tree[tree[y].child[0]].fa=0;tree[y].child[0]=0;pushup(y);//然后切断y与x的边,使其独立成两片splay森林(独立成两棵树)
return 0;
}


其他操作,这里以Add和query为代表

inline int Add(int x,int y,int val){
if(findroot(x)!=findroot(y)) return -1;//保险起见,特判
reverse(x);access(y);splay(y);add(y,val);
return 0;//如果忘了换根之后树的结构变化的同学,可以再看一下前面的图,显然x换成根之后,再access一下y,splay一下y,y就变成了x到y这条链的splay树的根节点,然后就可以add了啦
}


query是很简单的,不做讲解(如果学懂了Add的话)

inline int query(int x,int y){
if(findroot(x)!=findroot(y)) return -1;
reverse(x);access(y);splay(y);
return tree[y].maxn;
}


完整LCT封装结构体代码

struct Link_Cut_Tree{
inline bool isroot(int x){
return tree[tree[x].fa].child[0]!=x&&tree[tree[x].fa].child[1]!=x;
}
inline void add(int loc,int val){
tree[loc].val+=val;tree[loc].maxn+=val;tree[loc].add+=val;
}
inline void pushup(int loc){
tree[loc].maxn=max(tree[tree[loc].child[0]].maxn,tree[tree[loc].child[1]].maxn);
tree[loc].maxn=max(tree[loc].maxn,tree[loc].val);
}
inline void pushdown(int loc){
if(tree[loc].rev){
tree[loc].rev^=1;tree[tree[loc].child[0]].rev^=1;tree[tree[loc].child[1]].rev^=1;
swap(tree[loc].child[0],tree[loc].child[1]);
}
if(tree[loc].add){
if(tree[loc].child[0]) add(tree[loc].child[0],tree[loc].add);
if(tree[loc].child[1]) add(tree[loc].child[1],tree[loc].add);
tree[loc].add=0;
}
}
inline void Pushdown(int loc){
if(!isroot(loc)) Pushdown(tree[loc].fa);
pushdown(loc);
}
inline void rotate(int x){
int y=tree[x].fa,z=tree[y].fa,l,r;
if(tree[y].child[0]==x)l=0;else l=1;r=l^1;
if(!isroot(y)){
if(tree[z].child[0]==y) tree[z].child[0]=x;
else tree[z].child[1]=x;
}
tree[x].fa=z;tree[y].fa=x;tree[y].child[l]=tree[x].child[r];
tree[tree[x].child[r]].fa=y;tree[x].child[r]=y;
pushup(y);pushup(x);
}
inline void splay(int x){
Pushdown(x);
while(!isroot(x)){
int y=tree[x].fa,z=tree[y].fa;
if(!isroot(y)){
if(tree[y].child[0]==loc^tree[z].child[0]) rotate(loc);
else rotate(y);
}
rotate(loc);
}
}
inline void access(int loc){
for(register int i=0;loc;i=loc,loc=tree[loc].fa){
splay(loc);
tree[loc].child[1]=i;
pushup(loc);
}
}
inline int findroot(int loc){
access(loc);splay(loc);
while(tree[loc].child[0]) loc=tree[loc].child[0];
return loc;
}
inline void reverse(int loc){
access(loc);splay(loc);tree[loc].rev^=1;
}
inline int link(int x,int y){
if(findroot(x)==findroot(y)||x==y) return -1;
reverse(x);tree[x].fa=y;
return 0;
}
inline int cut(int x,int y){
if(findroot(x)!=findroot(y)||x==y) return -1;
reverse(x);access(y);splay(y);
tree[tree[y].child[0]].fa=0;tree[y].child[0]=0;pushup(y);
return 0;
}
inline int Add(int x,int y,int val){
if(findroot(x)!=findroot(y)) return -1;
reverse(x);access(y);splay(y);add(y,val);
return 0;
}
inline int query(int x,int y){
if(findroot(x)!=findroot(y)) return -1;
reverse(x);access(y);splay(y);
return tree[y].maxn;
}
}LCT;


下面是一道例题 HDOJ 4010 我保证我坚决不再刷杭电OJ的题了,太坑了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
const int MAXN=300010;
using namespace std;
inline int readin(){
int sum=0,fg=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')fg=-1;c=getchar();}
while(c>='0'&&c<='9'){sum=sum*10+c-'0';c=getchar();}
return sum*fg;
}
struct lct{
int fa,child[2],rev,add;
int val,maxn;
}tree[MAXN];
int n,Q;
struct Link_Cut_Tree{
inline bool isroot(int x){
return tree[tree[x].fa].child[0]!=x&&tree[tree[x].fa].child[1]!=x;
}
inline void add(int loc,int val){
tree[loc].val+=val;tree[loc].maxn+=val;tree[loc].add+=val;
}
inline void pushup(int loc){
tree[loc].maxn=max(tree[tree[loc].child[0]].maxn,tree[tree[loc].child[1]].maxn);
tree[loc].maxn=max(tree[loc].maxn,tree[loc].val);
}
inline void pushdown(int loc){
if(tree[loc].rev){
tree[loc].rev^=1;tree[tree[loc].child[0]].rev^=1;tree[tree[loc].child[1]].rev^=1;
swap(tree[loc].child[0],tree[loc].child[1]);
}
if(tree[loc].add){
if(tree[loc].child[0]) add(tree[loc].child[0],tree[loc].add);
if(tree[loc].child[1]) add(tree[loc].child[1],tree[loc].add);
tree[loc].add=0;
}
}
inline void Pushdown(int loc){
if(!isroot(loc)) Pushdown(tree[loc].fa);
pushdown(loc);
}
inline void rotate(int x){
int y=tree[x].fa,z=tree[y].fa,l,r;
if(tree[y].child[0]==x)l=0;else l=1;r=l^1;
if(!isroot(y)){
if(tree[z].child[0]==y) tree[z].child[0]=x;
else tree[z].child[1]=x;
}
tree[x].fa=z;tree[y].fa=x;tree[y].child[l]=tree[x].child[r];
tree[tree[x].child[r]].fa=y;tree[x].child[r]=y;
pushup(y);pushup(x);
}
inline void splay(int x){
Pushdown(x);
while(!isroot(x)){
int y=tree[x].fa,z=tree[y].fa;
if(!isroot(y)){
if((tree[y].child[0]==x)^(tree[z].child[0]==y)) rotate(x);
else rotate(y);
}
rotate(x);
}
}
inline void access(int loc){
for(register int i=0;loc;i=loc,loc=tree[loc].fa){
splay(loc);
tree[loc].child[1]=i;
pushup(loc);
}
}
inline int findroot(int loc){
access(loc);splay(loc);
while(tree[loc].child[0]) loc=tree[loc].child[0];
return loc;
}
inline void reverse(int loc){
access(loc);splay(loc);tree[loc].rev^=1;
}
inline int link(int x,int y){
if(findroot(x)==findroot(y)||x==y) return -1;
reverse(x);tree[x].fa=y;
return 0;
}
inline int cut(int x,int y){
if(findroot(x)!=findroot(y)||x==y) return -1;
reverse(x);access(y);splay(y);
tree[tree[y].child[0]].fa=0;tree[y].child[0]=0;pushup(y);
return 0;
}
inline int Add(int x,int y,int val){
if(findroot(x)!=findroot(y)) return -1;
reverse(x);access(y);splay(y);add(y,val);
return 0;
}
inline int query(int x,int y){
if(findroot(x)!=findroot(y)) return -1;
reverse(x);access(y);splay(y);
return tree[y].maxn;
}
}LCT;
struct Line{
int to,nxt;
}line[MAXN];
int head[MAXN],tail;
void add_line(int from,int to){
tail++;
line[tail].nxt=head[from];
line[tail].to=to;
head[from]=tail;
}
void dfs(int u,int fa){
for(register int i=head[u];i;i=line[i].nxt){
int v=line[i].to;
if(v!=fa){
tree[v].fa=u;
dfs(v,u);
}
}
}
void init(){
memset(head,0,sizeof(head));tail=0;
memset(tree,0,sizeof(tree));
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
init();
int from,to;
for(int i=1;i<=n-1;i++){
from=readin();to=readin();
add_line(from,to);add_line(to,from);
}
dfs(1,0);
int temp;
for(int i=1;i<=n;i++){
temp=readin();
tree[i].val=tree[i].maxn=temp;
}
Q=readin();
while(Q--){
int opt;
opt=readin();
int x,y,z;
switch(opt){
case 1: x=readin();y=readin();if(LCT.link(x,y)==-1) printf("-1\n");break;
case 2: x=readin();y=readin();if(LCT.cut(x,y)==-1)printf("-1\n");break;
case 3: z=readin();x=readin();y=readin();if(LCT.Add(x,y,z)==-1)printf("-1\n");break;
case 4: x=readin();y=readin();printf("%d\n",LCT.query(x,y));break;
}
}
puts("");
}
return 0;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: