目录

[学习笔记]LCT

简介

用于解决「动态树」问题。即对一棵树进行加边、删边操作,保证操作后仍是一棵树 (或一棵森林),并维护树链、子树信息。

具体的方式是将原树 / 森林划分成若干条链,并用一棵 Splay 维护一条链,不同的 Splay 之间按照原树结构连边。

特点是好维护链信息,不好维护子树信息。


变量定义

1
2
int fa[_], ch[_][2], val[_], sum[_];
bool tag[_];

fa[x]:x 在 Splay 上的父亲。对于 Splay 的根节点来说,就是该棵 Splay 中最浅的节点在原树中的父亲。

ch[x][0/1]:x 在 Splay 中的左 / 右儿子。

val[x]:x 的点权。

sum[x]:x 在 Splay 中的子树权值和。

tag[x]:x 的子树是否要交换左右儿子。(用于 MakeRoot 函数)


宏定义

1
2
#define get(x) (x == ch[fa[x]][1])
#define isroot(x) (x != ch[fa[x]][0] and x != ch[fa[x]][1])

get(x):x 是 fa[x] 的左 / 右儿子。

isroot(x):x 是否是它所在 Splay 的根节点。


主要函数

pushup & pushdown

pushup(x):从 x 的子节点更新 x 的信息。

pushdown(x):下放 x 的 tag。


update

1
2
3
4
void update(int x) {
  if (!isroot(x)) update(fa[x]);
  pushdown(x);
}

将 Splay 中 x 的所有祖先的 tag 从上往下下放。用于 Splay 操作之前。


Rotate

1
2
3
4
5
6
7
8
9
void Rotate(int x) {
  assert(fa[x]);
  int y = fa[x], z = fa[y], k = get(x), t = get(y);
  if (!isroot(y)) ch[z][t] = x;
  ch[y][k] = ch[x][!k], fa[ch[x][!k]] = y;
  ch[x][!k] = y, fa[y] = x;
  fa[x] = z;
  pushup(y), pushup(x);
}

和 Splay 的 Rotate 差不多,唯一的区别就是这句话

1
if (!isroot(y)) ch[z][t] = x;

要写在 fa[y]= x; 前面。


Splay

1
2
3
4
5
6
7
void Splay(int x) {
  update(x);
  while (!isroot(x)) {
    if (!isroot(fa[x]) and get(fa[x]) == get(x)) Rotate(fa[x]);
    Rotate(x);
  }
}

就是普通的 Splay。记得要先 update(x)


Access

1
2
3
4
5
int Access(int x) {
  int p = 0;
  while (x) Splay(x), ch[x][1] = p, pushup(x), p = x, x = fa[x];
  return p;
}

将 x 到原树上根节点的这条链放到同一棵 Splay 里。

p 表示 x 最后一次经过的 Splay 的根节点,也就是原树的根节点所在的 Splay 的根节点。

要注意 pushup(x)


MakeRoot

1
2
3
4
void MakeRoot(int x) {
  x = Access(x);
  swap(ch[x][0], ch[x][1]), tag[x] ^= 1;
}

将 x 变为原树的根。附加效果是使得 x 和它在原树中的子节点不在同一棵 Splay 中。

先将 x 和根放到一棵 Splay 中,然后将 x 变为原树的根就相当于将这条链上的点按照深度从深到浅重新排序,那么也就相当于把这棵 Splay 中的点交换左右儿子。


Split

1
2
3
void Split(int x, int y) {
  MakeRoot(x), Access(y), Splay(y);
}

将原树上 x 到 y 的链单独抠出来。

具体操作就是先将 x 变为原树的根,然后把 x 到 y 的链单独放到一棵 Splay 上。

最后是否要 Splay(y) 因具体情况而定。


Find

1
2
3
4
5
6
int Find(int x) {
  x = Access(x);
  while (ch[x][0]) pushdown(x), x = ch[x][0];
  Splay(x);
  return x;
}

找 x 所在原树的根节点 rt。可用于判断连通性。

先把 x 和 rt 放到同一棵 Splay 上,然后从这棵 Splay 的根节点开始一直往左跳,找到的最浅的节点即为 rt。

最后记得 Splay(x) 以保证 Splay 的复杂度。


1
2
3
4
void Link(int x, int y) {
  if (Find(x) == Find(y)) return;
  MakeRoot(x), Splay(x), fa[x] = y;
}

连接边 (x,y)。

先判断连通性。

然后将 x 变为原树的根,接着将它变为该 Splay 中的根,然后单向连接。


Cut

1
2
3
4
5
6
7
void Cut(int x, int y) {

  MakeRoot(x), Access(y), Splay(y);
  if (ch[y][0] != x or ch[x][1]) return;
  fa[x] = ch[y][0] = 0;
  pushup(y);
}

断开边 (x,y)。

先判断是否有 (x,y) 这条边。第一行三个函数相当于 Split(x,y),将 (x,y) 这条链抠出来后,若存在比 x 深且比 y 浅的点,说明不存在 (x,y) 这条边。

然后 ch[y][0] != x 这句话顺便把不连通的情况给判掉了。

若存在 (x,y) 这条边,则双向断开。

记得 pushup(y)


代码

[luoguP3690]【模板】Link Cut Tree (动态树)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
#include <cassert>
#include <cstdio>
#include <iostream>

using namespace std;

const int _ = 1e5 + 7;

struct LCT {
#define get(x) (x == ch[fa[x]][1])
#define isroot(x) (x != ch[fa[x]][0] and x != ch[fa[x]][1])

  int fa[_], ch[_][2], val[_], sum[_];
  bool tag[_];

  void pushup(int x) { sum[x] = sum[ch[x][0]] ^ sum[ch[x][1]] ^ val[x]; }

  void pushdown(int x) {
    if (tag[x]) {
      if (ch[x][0]) swap(ch[ch[x][0]][0], ch[ch[x][0]][1]), tag[ch[x][0]] ^= 1;
      if (ch[x][1]) swap(ch[ch[x][1]][0], ch[ch[x][1]][1]), tag[ch[x][1]] ^= 1;
      tag[x] = 0;
    }
  }

  void update(int x) {
    if (!isroot(x)) update(fa[x]);
    pushdown(x);
  }

  void Rotate(int x) {
    assert(fa[x]);
    int y = fa[x], z = fa[y], k = get(x), t = get(y);
    if (!isroot(y)) ch[z][t] = x;
    ch[y][k] = ch[x][!k], fa[ch[x][!k]] = y;
    ch[x][!k] = y, fa[y] = x;
    fa[x] = z;
    pushup(y), pushup(x);
  }

  void Splay(int x) {
    update(x);
    while (!isroot(x)) {
      if (!isroot(fa[x]) and get(fa[x]) == get(x)) Rotate(fa[x]);
      Rotate(x);
    }
  }

  int Access(int x) {
    int p = 0;
    while (x) Splay(x), ch[x][1] = p, pushup(x), p = x, x = fa[x];
    assert(!fa[p]);
    return p;
  }

  void MakeRoot(int x) {
    x = Access(x);
    assert(x), assert(!fa[x]);
    swap(ch[x][0], ch[x][1]), tag[x] ^= 1;
  }
    
  void Split(int x, int y) {
    MakeRoot(x), Access(y), Splay(y);
  }

  int Find(int x) {
    x = Access(x);
    while (ch[x][0]) pushdown(x), x = ch[x][0];
    Splay(x);
    return x;
  }

  void Link(int x, int y) {
    if (Find(x) == Find(y)) return;
    MakeRoot(x), Splay(x), fa[x] = y;
  }
  
  void Cut(int x, int y) {
    MakeRoot(x), Access(y), Splay(y);
    if (ch[y][0] != x or ch[x][1]) return;
    fa[x] = ch[y][0] = 0;
    pushup(y);
  }

  void Modify(int x, int w) {
    Splay(x), val[x] = w, pushup(x);
  }

  int Query(int x, int y) {
    MakeRoot(x);
    y = Access(y);
    return sum[y];
  }
} S;

int n, Q; 

int main() {
  cin >> n >> Q;
  for (int i = 1; i <= n; ++i) scanf("%d", &S.val[i]), S.sum[i] = S.val[i];
  for (int i = 1, ty, x, y; i <= Q; ++i) {
    scanf("%d%d%d", &ty, &x, &y);
    if (ty == 0) printf("%d\n", S.Query(x, y));
    if (ty == 1) S.Link(x, y);
    if (ty == 2) S.Cut(x, y);
    if (ty == 3) S.Modify(x, y);
  }
  return 0;
}