Link Cut Tree(LCT )学习笔记

在数据结构中有一类问题叫做动态树问题 DynamicTree ,它会要求你对一颗树进行切割和拼接,然后再在上面维护传统的数据结构能维护的值,为了完成这一类问题,就有了很多相应的算法来解决这类问题,Link Cut Tree就是其中一种比较方便实用的算法。

由于本文主要写的是有关操作,所以具体的算法内容只作简单说明,详细的介绍可以参考《QTREE解法的一些研究》中对LCT的解释。

简单的讲一讲,其实 LCT 的核心就是 Access 操作,取出一个点到根的链。再用一种可合并的平衡树来维护这条链的信息,平衡树中一个点的做左儿子就是在原树中这个点上方的节点,相应的右节点就是下方的节点,而我们选择用 Splay 来作为实现他的数据结构。其实差不多就是拿 Splay 维护重链的树链剖分
接下来解就是本文的关键。

如果需要维护一些值时,可以用Splay直接维护。
注意:这里给的模板都是把LCT和原树一起记录的。(在IsRoot中会有所说明)

Update / Push(上传和下传标记)

根据题目的需要来维护,跟线段树维护差不多。

IsRoot(判断当前点是否是所在平衡树的根)

设当前节点为 Now 。当它不是根时, Pre[Now] 就表示平衡树中的父亲,而真正的父亲根据平衡树的定义在当前平衡树中找到。当它是根时, Pre[Now] 就表示原树中它所在 Splay 中深度最小的点的父亲。
Son 表示的是平衡树中的儿子,那么显然 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

把一个节点 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

其实 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);
}
GetFa(找原树中的父亲节点)

假设是要求 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;
}
MakeRoot(换根操作)

顾名思义,把一个节点变成当前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(连接两棵树)

很简单,直接把一棵树变为它所在树的根,在把这整颗树接到另一棵树上。

//Link
void Link(int u, int v) {
    MakeRoot(u), Pre[u] = v;
}
Cut(切断一条边)

假设我们要切断 u v的边。由于它们所在的不是一颗普通的树,所以不能简单的表示它们之间的边。我们先要把它们丢进同一颗平衡树里面。显然的,可以 u MakeRoot,那么现在 u 必定是v的父亲(原来不确定),再把v进行 Access 。现在可以保证的是,在当前的平衡树中只有 uv 两个点,把 v <script type="math/tex" id="MathJax-Element-51">v</script>旋转到所在平衡树的根确定平衡树中的位置后,直接断开即可。

//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 Merge(int l, int r) {
    Splay(Get(l), 0); Splay(Get(r), Root);
}

void Insert(int post, int num) {
    Merge(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) {
    Merge(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) {
    Merge(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) {
    Merge(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) {
    Merge(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[N]] + a);
        }
    }
    printf("%d", (Ans == Inf) ? -1 : Ans);
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值