在数据结构中有一类问题叫做动态树问题 (DynamicTree) ,它会要求你对一颗树进行切割和拼接,然后再在上面维护传统的数据结构能维护的值,为了完成这一类问题,就有了很多相应的算法来解决这类问题,Link Cut Tree就是其中一种比较方便实用的算法。
由于本文主要写的是有关操作,所以具体的算法内容只作简单说明,详细的介绍可以参考《QTREE解法的一些研究》中对LCT的解释。
简单的讲一讲,其实 LCT 的核心就是 Access 操作,取出一个点到根的链。再用一种可合并的平衡树来维护这条链的信息,平衡树中一个点的做左儿子就是在原树中这个点上方的节点,相应的右节点就是下方的节点,而我们选择用 Splay 来作为实现他的数据结构。其实差不多就是拿 Splay 维护重链的树链剖分
接下来解就是本文的关键。
如果需要维护一些值时,可以用Splay直接维护。
注意:这里给的模板都是把LCT和原树一起记录的。(在IsRoot中会有所说明)
根据题目的需要来维护,跟线段树维护差不多。
设当前节点为 Now 。当它不是根时, Pre[Now] 就表示平衡树中的父亲,而真正的父亲根据平衡树的定义在当前平衡树中找到。当它是根 Pre[Now] 就表示原树中的父亲。
而 Son 表示的是平衡树中的儿子,那么显然 Now 只要不是 Pre[Now] 的其中一个儿子,那么它就是根。
//IsRoot
bool IsRoot(int Now) {
return Son[Pre[Now]][0] != Now && Son[Pre[Now]][1] != Now;
}
通过交换两个儿子来改变位置关系,再把标记下传实现序列翻转。
//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 后就可以对整颗平衡树进行操作,询问或是进行一些别的处理。
这是学会 LCT 的基础,不做详细解释。
给出一种简洁的打法。
//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 就是把原树中的一个点到根的路径上的点放到同一颗平衡树中(这颗平衡树中没有其他点)。
LCT 的和核心操作,这里只是最基础的版本,如要实现其他更能可以自行添加。
Now 表示当前 Access 跳到的节点, t 表示已经构造完的节点组成的平衡树。现在考虑怎么把t和 Now 合并起来。
由于一颗平衡树中的节点构成的都是一条指向根的链(不一定包括根),在 Now 所在平衡树中 Now 上方的点(原树)都是我们会跳到的,这些节点对我们是有用的,而没用的就是 Now 所在的平衡树中在 Now 下方的点(原树),只需删去就好了。
可以把 Now 转到平衡树的根( Splay ),那么它的右儿子就是在原树中在它下方的点构成的平衡树,只要把这棵树换成t就可以把两条链所在的平衡树和并起来。
代码比较简洁。
//Access
void Access(int Now) {
for (int t = 0; Now; Son[Now][1] = t, t = Now, Now = Pre[Now]) Splay(Now);
}
假设是要求 Now 的父亲。当然,首先就是把 Now 和 Now 的父亲放到一颗平衡树中( Access ),把 Now 旋到当前平衡树的根后,就是要找在我上面的最下面的点。那么根据 Son 的定义跑一遍就行了。
//GetFa
int GetFa(int Now) {
Access(Now), Splay(Now);
Now = Son[Now][0];
while (Son[Now][1]) Now = Son[Now][1];
return Now;
}
顾名思义,把一个节点变成当前LCT根节点,假设这个节点是Now。
我们发现,当把 Now 作为新的根时,只有 Now 到根路径上的从属关系发生了改变(就是儿子和父亲),而且更优美的是, Now 变成新的根后,这种从属关系刚好翻转了。举个例子,设Pre[i]为i节点的父亲节点,那么假如原来的关系是
Pre[a1]=a2,Pre[a2]=a3,Root=a3
a1 作为根后就变成了
Pre[a3]=a2,Pre[a2]=a1,Root=a1
这就对应了Reverse操作。
所以我们可以把 Now 到根路径上的点找出来( Access ),再把 Now 旋到对应平衡树的根( Splay ),再对这可平衡树打一个翻转标记,就玩成了还根操作。
//MakeRoot
void MakeRoot(int Now) {
Access(Now); Splay(Now); Rev[Now] ^= 1;
}
很简单,直接把一棵树变为它所在树的根,在把这整颗树接到另一棵树上。
//Link
void Link(int u, int v) {
MakeRoot(u), Pre[u] = v;
}
假设我们要切断 u 和 v 的边。由于它们所在的不是一颗普通的树,所以不能简单的表示它们之间的边。我们先要把它们丢进同一颗平衡树里面。显然的,可以 u 先 MakeRoot ,那么现在 u 必定是v的父亲(原来不确定),再把 v 进行 Access 。现在可以保证的是,在当前的平衡树中只有 u,v 两个点,把 v 旋转到所在平衡树的根确定平衡树中的位置后,直接断开即可。
//Cut
void Cut(int u, int v) {
MakeRoot(u), Access(v), Splay(v);
Pre[u] = Son[v][0] = 0;
}
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();
}
}
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[N]] + a);
}
}
printf("%d", (Ans == Inf) ? -1 : Ans);
}