Link-Cut Tree
2017-06-20 19:13
337 查看
Link-Cut Tree动态树
好难啊o(>﹏<)o
目录
概念与应用
算法思想
核心功能的实现
代码
LCT与树剖的区别在于
但虽然LCT每次基本操作复杂度为
LCT中首尾相连的实边组成一个路径。路径中深度最大的节点为路径的头部,深度最小的节点为路径的尾部;
LCT中,对于每条路径,都有一个按深度大小维护的splay来保存信息。类似树剖的性质,任意两条路径间有且只有一条虚边将其相连。所以只需要再单独考虑虚边的维护就可以了;
普通splay的
有了LCT,动态操作物理上并不对原森林进行修改,只是借助splay的分裂与合并,进行逻辑上的修改;
其实,即使是对原森林没有修改的查询操作,LCT也会对splay进行分裂与合并,使要访问的点位于同一路径上(反正LCT里的实边可以随意更改),方便后续操作;
但需要特别说明的是,LCT 并不支持对于子树的操作,但仍可以通过维护虚边的信息来完成一些简单的操作。
将u到root的“路径”(通常说的简单路径)变为路径(LCT里特指的有相连实边组成的路径)u下方的点到u的路径变为虚边。
实现:
将从u不断向上找父亲,并将其变为父亲的儿子(即将这些点放在同一颗splay里)。
将u变为原树的根。
实现:
先Access(u),再Reverse(u),把这颗splay中节点的深度全部翻转。
将u,v加入到同一颗splay中。
实现:
先Make Root(u),再Access(v),很好理解。
ps:这样还隐式的让u为合并后的splay的根节点,v为u的儿子(右)。
查找u所在原树的根节点,即判断u在哪颗树里,通常用于判断两点在原树是否相连。
实现:
先Access(u),再把Splay(u)到根,之后一直找左儿子(深度比u小的点),找到最后即为深度最小的点——root。
功能函数
在原森林中连一条边(u,v)。
实现:
先Make Root(u),让u为所在原树的根节点(对原树结构其实毫无影响,只是所在路径相对深度大小改变),再让v为u的父亲(即在LCT中连一条虚边(u,v))。
在原树中将边(u,v)断开。
实现:
先Splay Merge(u,v),将u,v放于同一颗splay中,便于操作,再把splay边(u,v)断开。
修改或询问原树中的一些信息,eg.修改val(u),询问路径(u,v)的点权和等。
实现:
对于修改(一般都是单点修改),通常都要先Access(u),再Splay(u)到根节点再改,方便更新;
对于查询(如路径点权和),提前在每个点里记录一下路径头部到该节点的点权和,直接Splay Merge(u,v),输出sum(u),即可,注意操作时及时更新信息。
注意开读入优化,不然会TLE!
好难啊o(>﹏<)o
目录
概念与应用
算法思想
核心功能的实现
代码
概念与应用
动态树LCT是维护动态森林的一种数据结构,支持树的合并link,
拆分cut(正如其名),
换根make root(我喜欢把这个函数叫be root~(≧▽≦)/~),
动态LCA(不会╮(╯﹏╰)╭),和所有树链剖分能支持(不针对子树)的操作;
LCT与树剖的区别在于
树剖以线段树为基础,而
LCT以splay(按原节点深度维护)为基础,这使得LCT相较前者可以支持动态的操作;
但虽然LCT每次基本操作复杂度为
均摊O(logn),由于其
常数较大,比树剖略慢。
算法思想
LCT中也有所谓的轻重链的概念(应该叫实虚边,也有叫偏爱边的),但其并划分以子树大小为根据,而是在操作的过程中进行不断修改的;LCT中首尾相连的实边组成一个路径。路径中深度最大的节点为路径的头部,深度最小的节点为路径的尾部;
LCT中,对于每条路径,都有一个按深度大小维护的splay来保存信息。类似树剖的性质,任意两条路径间有且只有一条虚边将其相连。所以只需要再单独考虑虚边的维护就可以了;
普通splay的
fa(root)==0,但在这里,我们可以令一个splay的root——rti的父亲为其所在实边尾部节点的父亲。这不仅不会对其它操作有任何影响,而且还机智地记录了路径之间的关系,即虚边的信息,于是原森林的信息得到完整保存;
有了LCT,动态操作物理上并不对原森林进行修改,只是借助splay的分裂与合并,进行逻辑上的修改;
其实,即使是对原森林没有修改的查询操作,LCT也会对splay进行分裂与合并,使要访问的点位于同一路径上(反正LCT里的实边可以随意更改),方便后续操作;
但需要特别说明的是,LCT 并不支持对于子树的操作,但仍可以通过维护虚边的信息来完成一些简单的操作。
核心功能的实现
底层函数Access接驳 //access不仅有访问的意思,这里其实应该是接驳的意思,即无缝连接(照搬词典O(∩_∩)O)
将u到root的“路径”(通常说的简单路径)变为路径(LCT里特指的有相连实边组成的路径)u下方的点到u的路径变为虚边。
实现:
将从u不断向上找父亲,并将其变为父亲的儿子(即将这些点放在同一颗splay里)。
Make Root换根
将u变为原树的根。
实现:
先Access(u),再Reverse(u),把这颗splay中节点的深度全部翻转。
Splay Mergesplay合并 //不知道大家为啥叫它split,那不是分裂的意思吗?(⊙v⊙)
将u,v加入到同一颗splay中。
实现:
先Make Root(u),再Access(v),很好理解。
ps:这样还隐式的让u为合并后的splay的根节点,v为u的儿子(右)。
Find Root查找根
查找u所在原树的根节点,即判断u在哪颗树里,通常用于判断两点在原树是否相连。
实现:
先Access(u),再把Splay(u)到根,之后一直找左儿子(深度比u小的点),找到最后即为深度最小的点——root。
功能函数
Link连接
在原森林中连一条边(u,v)。
实现:
先Make Root(u),让u为所在原树的根节点(对原树结构其实毫无影响,只是所在路径相对深度大小改变),再让v为u的父亲(即在LCT中连一条虚边(u,v))。
Cut断开
在原树中将边(u,v)断开。
实现:
先Splay Merge(u,v),将u,v放于同一颗splay中,便于操作,再把splay边(u,v)断开。
Modify/Query修改或询问
修改或询问原树中的一些信息,eg.修改val(u),询问路径(u,v)的点权和等。
实现:
对于修改(一般都是单点修改),通常都要先Access(u),再Splay(u)到根节点再改,方便更新;
对于查询(如路径点权和),提前在每个点里记录一下路径头部到该节点的点权和,直接Splay Merge(u,v),输出sum(u),即可,注意操作时及时更新信息。
代码
洛谷P3690 模板题注意开读入优化,不然会TLE!
#include <cstdio> #include <algorithm> using namespace std; inline int read(); const int MAXN=300005; int n,m; struct node{ int s[2],fa,w,xr; bool rev; }; class LCT{ private: int s[MAXN],top; node d[MAXN]; void upd(int u){ d[u].xr=d[d[u].s[0]].xr^d[d[u].s[1]].xr^d[u].w; } void push_d(int u){ if(!d[u].rev) return; d[d[u].s[0]].rev^=1; d[d[u].s[1]].rev^=1; swap(d[u].s[0],d[u].s[1]); d[u].rev=0; } bool jud_rt(int u){ return ((d[d[u].fa].s[0]!=u) && (d[d[u].fa].s[1]!=u)); } void rot(int u){ int ufa=d[u].fa; push_d(ufa),push_d(u); bool lr= d[ufa].s[1]==u; d[u].fa=d[ufa].fa; if(!jud_rt(ufa)) d[d[u].fa].s[d[d[u].fa].s[1]==ufa]=u; d[ufa].s[lr]=d[u].s[lr^1]; d[d[ufa].s[lr]].fa=ufa; d[u].s[lr^1]=ufa; d[ufa].fa=u; upd(ufa),upd(u); } void spl(int u){ push_d(u); int bb3d &ufa=d[u].fa,&ugfa=d[ufa].fa; while(!jud_rt(u)){ if(!jud_rt(ufa)){ if((d[ufa].s[0]==u)^(d[ugfa].s[0]==ufa)) rot(u); else rot(ufa); } rot(u); } } void acc(int u){ for(int v=0;u;v=u,u=d[u].fa){ spl(u); d[u].s[1]=v; upd(u); } } void be_rt(int u){ acc(u); spl(u); d[u].rev^=1; } int find_rt(int u){ acc(u); spl(u); while(d[u].s[0]) u=d[u].s[0]; return u; } void mg_spl(int u,int v){ be_rt(u); acc(v); spl(v); } public: void add(int u,int val){ d[u].w=d[u].xr=val; } int qry_xr(int u,int v){ mg_spl(u,v); return d[v].xr; } void link(int u,int v){ int urt=find_rt(u),vrt=find_rt(v); if(urt==vrt) return; be_rt(u); d[u].fa=v; } void cut(int u,int v){ int urt=find_rt(u),vrt=find_rt(v); if(urt!=vrt) return; mg_spl(u,v); if(d[v].s[0]==u){ d[v].s[0]=0; d[u].fa=0; } } void mdf(int u,int x){ acc(u); spl(u); d[u].w=x; upd(u); } }T; int main(){ n=read(),m=read(); for(int i=1,tmp;i<=n;++i){ tmp=read(); T.add(i,tmp); } for(int i=1,opt,x,y;i<=m;++i){ opt=read(),x=read(),y=read(); switch(opt){ case 1: T.link(x,y); break; case 2: T.cut(x,y); break; case 3: T.mdf(x,y); break; default:printf("%d\n",T.qry_xr(x,y)); } } return 0; } inline int read(){ char c; int x; while(c=getchar(),c<'0' || '9'<c); x=c-'0'; while(c=getchar(),'0'<=c && c<='9') x=(x<<3)+(x<<1)+c-'0'; return x; }
相关文章推荐
- 【UOJ207】共价大爷游长沙(Link-Cut Tree,随机化)
- 【SPOJ】QTREE7(Link-Cut Tree)
- 【BZOJ2049】[Sdoi2008]Cave 洞穴勘测【Link-Cut Tree】
- 【BZOJ3669】[Noi2014]魔法森林【Link-Cut Tree】【最小生成树】
- 【poj3237】【link-cut tree】Tree
- link-cut tree
- Link-Cut Tree指针模板
- cogs1889 [SDOI2008]Cave 洞穴勘测 link-cut tree
- 【UOJ207】共价大爷游长沙(Link-Cut Tree,随机化)
- (link-cut tree)
- 【动态树初探】link-cut tree
- Link-Cut Tree
- BZOJ 题目1036: [ZJOI2008]树的统计Count(Link Cut Tree,改动点权求两个最大值和最大值)
- [BZOJ3282/LGOJ3690] Link-Cut Tree 模板题
- LCT(Link Cut Tree)总结
- BZOJ 3282 Tree ——Link-Cut Tree
- LCT(Link-Cut Tree)详解(蒟蒻自留地)
- 【Learning】一步步地解释Link-cut Tree
- 动态树之LCT(link-cut tree)讲解
- 【BZOJ1969】航线规划(Link-Cut Tree)