您的位置:首页 > 其它

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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: