Link Cut Tree(LCT )学习笔记
2016-03-27 13:24
465 查看
先来说一说什么是Link Cut Tree
在数据结构中有一类问题叫做动态树问题(DynamicTree)(Dynamic Tree),它会要求你对一颗树进行切割和拼接,然后再在上面维护传统的数据结构能维护的值,为了完成这一类问题,就有了很多相应的算法来解决这类问题,Link Cut Tree就是其中一种比较方便实用的算法。Link Cut Tree的实现
由于本文主要写的是有关操作,所以具体的算法内容只作简单说明,详细的介绍可以参考《QTREE解法的一些研究》中对LCT的解释。简单的讲一讲,其实LCTLCT的核心就是AccessAccess操作,取出一个点到根的链。再用一种可合并的平衡树来维护这条链的信息,平衡树中一个点的做左儿子就是在原树中这个点上方的节点,相应的右节点就是下方的节点,而我们选择用SplaySplay来作为实现他的数据结构。其实差不多就是拿SplaySplay维护重链的树链剖分
接下来解就是本文的关键。
有关Link Cut Tree的基础操作
如果需要维护一些值时,可以用Splay直接维护。注意:这里给的模板都是把LCT和原树一起记录的。(在IsRoot中会有所说明)
Update / Push(上传和下传标记)
根据题目的需要来维护,跟线段树维护差不多。IsRoot(判断当前点是否是所在平衡树的根)
设当前节点为NowNow。当它不是根时,Pre[Now]Pre[Now]就表示平衡树中的父亲,而真正的父亲根据平衡树的定义在当前平衡树中找到。当它是根Pre[Now]Pre[Now]就表示原树中的父亲。而SonSon表示的是平衡树中的儿子,那么显然NowNow只要不是Pre[Now]Pre[Now]的其中一个儿子,那么它就是根。
//IsRoot bool IsRoot(int Now) { return Son[Pre[Now]][0] != Now && Son[Pre[Now]][1] != Now; }
Reverse(翻转)
通过交换两个儿子来改变位置关系,再把标记下传实现序列翻转。//Reverse void Reverse(int Now) { if (Rev[Now]) { Rev[Son[Now][0]] ^= 1, Rev[Son[Now][1]] ^= 1; swap(Son[Now][0], Son[Now][1]); Rev[Now] = 0; } }
Splay
把一个节点SplaySplay后就可以对整颗平衡树进行操作,询问或是进行一些别的处理。这是学会LCTLCT的基础,不做详细解释。
给出一种简洁的打法。
//Splay void Rotate(int Now) { int Fa = Pre[Now], Gran = Pre[Fa], Side = (Son[Fa][1] == Now); if (!IsRoot(Fa)) Son[Gran][Son[Gran][1] == Fa] = Now; Pre[Fa] = Now, Pre[Now] = Gran; Son[Fa][Side] = Son[Now][!Side], Pre[Son[Fa][Side]] = Fa; Son[Now][!Side] = Fa; Update(Fa), Update(Now); } void Splay(int Now) { static int D[MAXN], top; D[top = 1] = Now; for (int p = Now; !IsRoot(p); p = Pre[p]) D[++ top] = Pre[p]; for (; top; top --) Reverse(D[top]); for (; !IsRoot(Now); Rotate(Now)) { int Fa = Pre[Now], Gran = Pre[Fa]; if (IsRoot(Fa)) continue; (Son[Fa][0] == Now) ^ (Son[Gran][0] == Fa) ? Rotate(Now) : Rotate(Fa); } Update(Now); }
Access
其实AccessAccess就是把原树中的一个点到根的路径上的点放到同一颗平衡树中(这颗平衡树中没有其他点)。LCTLCT的和核心操作,这里只是最基础的版本,如要实现其他更能可以自行添加。
NowNow表示当前AccessAccess跳到的节点,tt表示已经构造完的节点组成的平衡树。现在考虑怎么把t和NowNow合并起来。
由于一颗平衡树中的节点构成的都是一条指向根的链(不一定包括根),在NowNow所在平衡树中NowNow上方的点(原树)都是我们会跳到的,这些节点对我们是有用的,而没用的就是NowNow所在的平衡树中在NowNow下方的点(原树),只需删去就好了。
可以把NowNow转到平衡树的根(SplaySplay),那么它的右儿子就是在原树中在它下方的点构成的平衡树,只要把这棵树换成t就可以把两条链所在的平衡树和并起来。
代码比较简洁。
//Access void Access(int Now) { for (int t = 0; Now; Son[Now][1] = t, t = Now, Now = Pre[Now]) Splay(Now); }
GetFa(找原树中的父亲节点)
假设是要求NowNow的父亲。当然,首先就是把NowNow和NowNow的父亲放到一颗平衡树中(AccessAccess),把NowNow旋到当前平衡树的根后,就是要找在我上面的最下面的点。那么根据SonSon的定义跑一遍就行了。//GetFa int GetFa(int Now) { Access(Now), Splay(Now); Now = Son[Now][0]; while (Son[Now][1]) Now = Son[Now][1]; return Now; }
MakeRoot(换根操作)
顾名思义,把一个节点变成当前LCT根节点,假设这个节点是Now。我们发现,当把NowNow作为新的根时,只有NowNow到根路径上的从属关系发生了改变(就是儿子和父亲),而且更优美的是,NowNow变成新的根后,这种从属关系刚好翻转了。举个例子,设Pre[i]为i节点的父亲节点,那么假如原来的关系是
Pre[a1]=a2,Pre[a2]=a3,Root=a3Pre[a1] = a2, Pre[a2] = a3, Root = a3
a1a1作为根后就变成了
Pre[a3]=a2,Pre[a2]=a1,Root=a1Pre[a3] = a2, Pre[a2] = a1, Root = a1
这就对应了Reverse操作。
所以我们可以把NowNow到根路径上的点找出来(AccessAccess),再把NowNow旋到对应平衡树的根(SplaySplay),再对这可平衡树打一个翻转标记,就玩成了还根操作。
//MakeRoot void MakeRoot(int Now) { Access(Now); Splay(Now); Rev[Now] ^= 1; }
Link(连接两棵树)
很简单,直接把一棵树变为它所在树的根,在把这整颗树接到另一棵树上。//Link void Link(int u, int v) { MakeRoot(u), Pre[u] = v; }
Cut(切断一条边)
假设我们要切断uu和vv的边。由于它们所在的不是一颗普通的树,所以不能简单的表示它们之间的边。我们先要把它们丢进同一颗平衡树里面。显然的,可以uu先MakeRootMakeRoot,那么现在uu必定是v的父亲(原来不确定),再把vv进行AccessAccess。现在可以保证的是,在当前的平衡树中只有u,vu, v两个点,把vv旋转到所在平衡树的根确定平衡树中的位置后,直接断开即可。//Cut void Cut(int u, int v) { MakeRoot(u), Access(v), Splay(v); Pre[u] = Son[v][0] = 0; }
模板题
NOI2005 维护数列
splay模板题题目(BZOJ):http://www.lydsy.com/JudgeOnline/problem.php?id=1500
代码:
//NOI2005 维护数列 YxuanwKeith #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 1e6, Inf = -1e6; int N, M, Root, top, D[MAXN], A[MAXN]; int tot, Pre[MAXN], Son[MAXN][2], MaxL[MAXN], MaxR[MAXN], MaxM[MAXN], Val[MAXN], Size[MAXN], Same[MAXN], Sum[MAXN]; bool Rev[MAXN]; char S[20]; void Push(int Now) { if (!Now) return; if (Same[Now] != Inf) { Val[Now] = Same[Now], Sum[Now] = Size[Now] * Val[Now]; if (Same[Now] >= 0) MaxL[Now] = MaxR[Now] = MaxM[Now] = Sum[Now]; else MaxL[Now] = MaxR[Now] = 0, MaxM[Now] = Val[Now]; Same[Son[Now][0]] = Same[Son[Now][1]] = Same[Now]; Same[Now] = Inf; } if (Rev[Now]) { Rev[Son[Now][0]] ^= 1, Rev[Son[Now][1]] ^= 1; swap(Son[Now][0], Son[Now][1]), swap(MaxL[Now], MaxR[Now]); Rev[Now] = 0; } } void Updata(int Now) { int l = Son[Now][0], r = Son[Now][1]; Push(l), Push(r); Size[Now] = Size[l] + Size[r] + 1, Sum[Now] = Val[Now] + Sum[l] + Sum[r]; MaxR[Now] = max(MaxR[r], Sum[r] + MaxR[l] + Val[Now]); MaxL[Now] = max(MaxL[l], Sum[l] + MaxL[r] + Val[Now]); MaxM[Now] = max(max(MaxM[l], MaxM[r]), MaxR[l] + MaxL[r] + Val[Now]); } int Get(int Goal) { int Now = Root; for (; ;) { Push(Now); int Num = Size[Son[Now][0]] + 1; if (Num == Goal) return Now; if (Num < Goal) Goal -= Num, Now = Son[Now][1]; else Now = Son[Now][0]; } } void Rotate(int Now) { int Fa = Pre[Now], Gran = Pre[Fa]; int Side = (Son[Fa][1] == Now); Pre[Now] = Gran, Pre[Fa] = Now; Son[Fa][Side] = Son[Now][!Side], Pre[Son[Fa][Side]] = Fa; Son[Now][!Side] = Fa; Son[Gran][Son[Gran][1] == Fa] = Now; Updata(Fa); Updata(Now); } void Splay(int Now, int Goal) { for (Push(Now); Pre[Now] != Goal; Rotate(Now)) { int Fa = Pre[Now], Gran = Pre[Fa]; if (Pre[Fa] == Goal) continue; (Son[Gran][0] == Fa) ^ (Son[Fa][0] == Now) ? Rotate(Now) : Rotate(Fa); } Updata(Now); if (!Goal) Root = Now; } int Build(int L, int R, int fa) { if (L > R) return 0; int nt = top ? D[top --] : ++ tot, Mid = (L + R) >> 1; Pre[nt] = fa, Val[nt] = A[Mid], Rev[nt] = 0, Same[nt] = Inf; Son[nt][0] = Build(L, Mid - 1, nt); Son[nt][1] = Build(Mid + 1, R, nt); Updata(nt); return nt; } void Mercy(int l, int r) { Splay(Get(l), 0); Splay(Get(r), Root); } void Insert(int post, int num) { Mercy(post + 1, post + 2); int Now = Son[Root][1], Rt = Build(1, num, Now); Son[Now][0] = Rt; Updata(Now), Updata(Root); } void Del(int Now) { if (!Now) return; D[++ top] = Now; Del(Son[Now][0]), Del(Son[Now][1]); } void Delete(int post, int num) { Mercy(post, post + num + 1); int Now = Son[Root][1]; Del(Son[Now][0]); Son[Now][0] = 0; Updata(Now), Updata(Root); } void MakeSame(int post, int num, int same) { Mercy(post, post + num + 1); int Now = Son[Root][1]; Push(Now); Same[Son[Now][0]] = same; Updata(Now), Updata(Root); } void Reverse(int post, int num) { Mercy(post, post + num + 1); int Now = Son[Root][1]; Push(Now); Rev[Son[Now][0]] ^= 1; Updata(Now), Updata(Root); } void GetSum(int post, int num) { Mercy(post, post + num + 1); int Now = Son[Son[Root][1]][0]; Push(Now); printf("%d\n", Sum[Now]); } void MaxSum() { Push(Root); printf("%d\n", MaxM[Root]); } int main() { scanf("%d%d", &N, &M); for (int i = 2; i <= N + 1; i ++) scanf("%d", &A[i]); MaxM[0] = A[1] = A[N + 2] = Inf; Root = Build(1, N + 2, 0); for (int i = 1; i <= M; i ++) { scanf("%s", S); int post, num, same; if (S[2] != 'X') scanf("%d%d", &post, &num); if (S[0] == 'I') { for (int i = 1; i <= num; i ++) scanf("%d", &A[i]); Insert(post, num); } if (S[2] == 'K') { scanf("%d", &same); MakeSame(post, num, same); } if (S[0] == 'D') Delete(post, num); if (S[0] == 'R') Reverse(post, num); if (S[0] == 'G') GetSum(post, num); if (S[2] == 'X') MaxSum(); } }
NOI2014 魔法森林
LCT模板题题目(BZOJ):http://www.lydsy.com/JudgeOnline/problem.php?id=3669
代码:
//NOI2014 魔法森林 YxuanwKeith #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 300005, Inf = 1e9 + 7; struct Node { int u, v, a, b;} Line[MAXN]; int N, M, Ans, Fa[MAXN]; int Max[MAXN], Pre[MAXN], Val[MAXN], Son[MAXN][2]; bool Rev[MAXN]; bool cmp(Node u, Node v) { return u.a < v.a;} int Find(int x) { return Fa[x] == x ? x : Fa[x] = Find(Fa[x]);} void Updata(int Now) { Max[Now] = Now; if (Val[Max[Now]] < Val[Max[Son[Now][0]]]) Max[Now] = Max[Son[Now][0]]; if (Val[Max[Now]] < Val[Max[Son[Now][1]]]) Max[Now] = Max[Son[Now][1]]; } bool IsRoot(int Now) { return Son[Pre[Now]][0] != Now && Son[Pre[Now]][1] != Now;} void Reverse(int Now) { if (Rev[Now]) { Rev[Son[Now][0]] ^= 1, Rev[Son[Now][1]] ^= 1; swap(Son[Now][0], Son[Now][1]); Rev[Now] = 0; } } void Rotate(int Now) { int Fa = Pre[Now], Gran = Pre[Fa], Side = (Son[Fa][1] == Now); if (!IsRoot(Fa)) Son[Gran][Son[Gran][1] == Fa] = Now; Pre[Fa] = Now, Pre[Now] = Gran; Son[Fa][Side] = Son[Now][!Side], Pre[Son[Fa][Side]] = Fa; Son[Now][!Side] = Fa; Updata(Fa), Updata(Now); } void Splay(int Now) { static int D[MAXN], top; D[top = 1] = Now; for (int p = Now; !IsRoot(p); p = Pre[p]) D[++ top] = Pre[p]; for (; top; top --) Reverse(D[top]); for (; !IsRoot(Now); Rotate(Now)) { int Fa = Pre[Now], Gran = Pre[Fa]; if (IsRoot(Fa)) continue; (Son[Fa][0] == Now) ^ (Son[Gran][0] == Fa) ? Rotate(Now) : Rotate(Fa); } Updata(Now); } void Access(int Now) { for (int t = 0; Now; Son[Now][1] = t, t = Now, Now = Pre[Now]) Splay(Now); } void MakeRoot(int Now) { Access(Now); Splay(Now); Rev[Now] ^= 1; } void Link(int u, int v) { MakeRoot(u), Pre[u] = v; } void Query(int u, int v) { MakeRoot(u), Access(v), Splay(v); } void Cut(int u, int v) { Query(u, v); Pre[u] = Son[v][0] = 0; } int main() { scanf("%d%d", &N, &M); for (int i = 1; i <= M; i ++) scanf("%d%d%d%d", &Line[i].u, &Line[i].v, &Line[i].a, &Line[i].b); for (int i = 1; i <= N; i ++) Fa[i] = i; sort(Line + 1, Line + 1 + M, cmp); Ans = Inf; for (int i = 1; i <= M; i ++) { int u = Line[i].u, v = Line[i].v, a = Line[i].a, b = Line[i].b; Val[i + N] = b; if (Find(u) == Find(v)) { Query(u, v); int Side = Max[v]; if (Val[i + N] <= Val[Side]) { Cut(Side, Line[Side - N].u), Cut(Side, Line[Side - N].v); Link(i + N, u), Link(i + N, v); } } else { Fa[Fa[u]] = v; Link(i + N, u), Link(i + N, v); } if (Find(1) == Find(N)) { Query(1, N); Ans = min(Ans, Val[Max ] + a); } } printf("%d", (Ans == Inf) ? -1 : Ans); }
相关文章推荐
- C语言完成2048,辣鸡代码(#滑稽#)
- 開始学习swift开发
- 财经世界(5)国际货币基金组织,世界银行,国际清算银行(BIS)与美联储
- Android-ListView两种适配器以及事件监听
- hihoCoder #1014 : Trie树
- Python lamda
- 跨域中的crossdomain文件
- 3.Utm详细实现-用户生命流程
- PAT1029旧键盘(20)
- 进程间通信之-信号signal--linux内核剖析(九)
- 字符串截取题型
- JSP proxool+mysql数据库连接池配置
- 设计模式之观察者模式
- lintcode:Subsets
- 1003
- 【机房重构】——VS2013程序打包
- 2000
- 《Linux 内核分析》第五周
- windows下mysql忘记root密码的解决方法
- 《Linux内核与分析》第五周