浅谈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等,下面会详细讲解
….
然后这棵树被划分了,也就是如图所示的Preferred_Path和普通的树边, Preferred_Path连起来的所有点是被一棵splay树维护的,而其他所有独立的点(比如上图的左边两点,右边的图中,都分别是一棵splay树,也就是只有一个节点splay树)
好吧大家感性认知一下就好,之后还要理性理解的,放心
显然如果现在我对z,y,x这棵平衡树,进行splay(y)操作,y变成了这棵平衡树的根节点,那么以深度来排平衡树的key值的话,z点是y的左儿子,x是y的右儿子,显然,z和x的father都是y,而y的father,则应该是w,这就是我们构造了辅助树(我们通常选用splay树作为这个辅助树),一定要分清father和son的关系
这个操作可以把一个指定的点到树拉通,但显然需要熟悉地写出splay树,那么下面我们来写splay树(如果很熟练的朋友可以直接跳这部分啦)
关于isroot函数
显然如果现在y是其所在splay树的根节点,那么他的fa一定是连向另一颗splay树的,显然对于另一个splay树的lson和rson都不会连回这棵树,否则就是一棵splay树了
接下来就是几个比较有趣的函数了
比如findroot函数,这个函数是要求loc所在的树(并非辅助树)的根
reverse函数
reverse也就是换根操作,也就是splay的区间翻转操作
link函数
显然,这个link函数是把两棵splay森林,也就是LCT连起来,如果x的root和y的root(注意是真正树结构的root,不是splay树的root)或者是x==y,那么说明不能连接,否则直接把x所在的树换根为x,然后再把x的fa设置成y即可[这里有一点值得注意,findroot的时候其实已经access并且splay过x了,所以直接reverse]
cut操作
其他操作,这里以Add和query为代表
query是很简单的,不做讲解(如果学懂了Add的话)
完整LCT封装结构体代码
下面是一道例题 HDOJ 4010 我保证我坚决不再刷杭电OJ的题了,太坑了
学过链剖的同学应该可以很快地理解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; }
相关文章推荐
- LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板
- 算法学习之:动态树(link-cut-tree)及bzoj3282Tree例题详解
- 动态树 LCT(Link-Cut-Tree)--入门教程
- BZOJ 2759 一个动态树好题 Link-Cut-Tree+扩展欧几里得
- Link-cut-tree 学习记录 & hdu4010
- [Luogu 3690]【模板】Link Cut Tree (动态树)
- BZOJ 2002 弹飞绵羊 Link-Cut-Tree(LCT)
- 洛谷.3690.[模板]Link Cut Tree(动态树)
- LCT(Link-Cut-Tree)学习
- Link Cut Tree(动态树)
- LCT link-cut tree Hdu 5002 Tree 2014鞍山网络赛
- luoguP3690 【模板】Link Cut Tree (动态树)
- BZOJ4025(LCT+LCT+LinkCutTree)
- BZOJ 2002 弹飞绵羊 Link-Cut-Tree(LCT)
- 【Link Cut Tree】动态树的理解(入门)
- bzoj3282&&luogu3690【模板】Link Cut Tree (动态树)
- 【动态树之link_cut_tree学习小记】
- 动态规划,计数DP,模拟(Persistent Link/cut Tree,HDU 5401)
- LCT (Link-Cut-Tree) 学习笔记
- 动态树(Link-Cut-Tree)简单总结(指针版本)