Link-Cut-Tree 的基本姿势
来源:互联网 发布:淘宝化工店 编辑:程序博客网 时间:2024/05/22 14:55
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 10005int 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; } }}
- Link-Cut-Tree 的基本姿势
- 【ZJOI2008】【link-cut tree】树的统计
- 【link-cut tree】
- Link Cut Tree (paint)
- link-cut-tree
- Link-Cut Tree
- Link/cut tree
- BZOJ2157【Link Cut Tree】
- 【笔记】Link-Cut-Tree
- (link-cut tree)
- Link-Cut-Tree总结
- Link/Cut Tree
- Link-Cut-Tree模板
- Link-Cut-Tree讲解
- link-cut tree预习
- A. Link/Cut Tree
- 【学习心得】Link-cut Tree
- Link-Cut Tree
- java入门教程-12.1Java网络编程之IP地址和InetAddress类
- java入门教程-12.2Java网络编程之统一资源定位符URL
- java入门教程-12.3Java 套接字(Socket)
- java入门教程-12.4Java程序与数据库连接
- java入门教程-12.5几个重要的java数据库访问类和接口
- Link-Cut-Tree 的基本姿势
- PhoneGap android开发:文件处理
- java入门教程-12.6Java数据库查询简介
- J抽象类继承问题
- java入门教程-12.7Java数据库更新
- java入门教程-12.8Java数据库之插入记录
- java入门教程-12.9Java数据库之修改记录
- JetBrains PhpStorm/WebStorm/PyCharm 注册码
- java入门教程-12.10Java数据库之删除记录