LCT 简述
LCT (Link Cut Tree) 是一种维护树链的灵活的数据结构。与线段树不同的是,LCT 一般使用 Splay 这种非常优雅、灵活的数据结构来维护树链。因为 LCT 支持动态增边、删边,所以很多题目就可以打破一些限制进行求解。
LCT 的主要操作:
- Pushup,Pushdown 操作:数据结构基本操作
- Rotate 操作:这个是 Splay 的旋转操作
- Splay 操作:这个是 Splay 的伸展操作
- Access 操作:将点\(p\)到 Splay 根的链打通
- MakeRoot 操作:将某个点作为树根
- Find 操作:找到当前 Splay 的根
- Link 操作:加边
- Cut 操作:删边
- Split 操作:提取一链
最小生成树中 LCT 的经典模型
LCT 的很多题目的模型基本都来源于 LCT 做最小生成树的方法。LCT 做最小生成树的方法与 Kruskal 非常的相似(虽然有很多不必要的步骤,但是这是经典模型的一部分),我在这里将进行介绍。
初始化好了一个 LCT 之后,我们把边从小到大进行排序,然后我们将条边视为一个点加上两条边,且点权即为边权:
考虑后面发现有环时(同一连通块时),进行检查:如果当前 LCT 中存在比当前大的边(当然排了序之后并不会出现这种情况),直接 Cut 操作掉这一点两边。
差值最小生成树
这个其实也运用到了 LCT 求最小生成树的思想。把边从小到大排序之后,枚举边的上界,并且在没有环的情况下进行 Link 操作;如果有环,那么删除当前连通块中最小的边,贪心地保持差值最小。当整棵树完整的时候(存在 n – 1 条边时),对答案进行更新即可。
// P4234.cpp #include <bits/stdc++.h> #define lson (ch[p][0]) #define rson (ch[p][1]) using namespace std; const int MAX_N = 50000 + 5, MAX_E = 200005; const long long INF = (1LL << 62); struct edge { int src, dst, weight, id; bool operator<(const edge &target) const { return weight < target.weight; } } edges[MAX_E << 1]; int n, m, fa[MAX_E << 1], idx[MAX_E << 1], ch[MAX_E << 1][2]; bool tag[MAX_E << 1], book[MAX_E << 1]; int check(int x) { return ch[fa[x]][1] == x; } bool isRoot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; } inline void update(int p) { idx[p] = p; if (idx[lson] > n && (idx[p] <= n || idx[p] > idx[lson])) idx[p] = idx[lson]; if (idx[rson] > n && (idx[p] <= n || idx[p] > idx[rson])) idx[p] = idx[rson]; } inline void pushdown(int p) { if (tag[p]) { if (lson) tag[lson] ^= 1, swap(ch[lson][0], ch[lson][1]); if (rson) tag[rson] ^= 1, swap(ch[rson][0], ch[rson][1]); tag[p] = 0; } } inline void rotate(int x) { int y = fa[x], z = fa[y], dir = check(x), w = ch[x][dir ^ 1]; fa[x] = z; if (!isRoot(y)) ch[z][check(y)] = x; ch[y][dir] = w, fa[w] = y; ch[x][dir ^ 1] = y, fa[y] = x; update(y), update(x), update(z); } inline void jump(int x) { if (!isRoot(x)) jump(fa[x]); pushdown(x); } inline void splay(int p) { jump(p); for (int fat = fa[p]; fat = fa[p], !isRoot(p); rotate(p)) if (!isRoot(fat)) rotate(check(fat) == check(p) ? fat : p); } inline void access(int p) { for (int fat = 0; p != 0; fat = p, p = fa[p]) splay(p), ch[p][1] = fat, update(p); } inline void makeRoot(int p) { access(p), splay(p); swap(lson, rson), tag[p] ^= 1; } inline int find(int p) { access(p), splay(p), pushdown(p); while (lson) pushdown(p = lson); splay(p); return p; } inline void link(int x, int y) { makeRoot(x); fa[x] = y; } inline void split(int x, int y) { makeRoot(x), access(y), splay(y); } inline bool judge(int x, int y) { makeRoot(x); return (find(y) == x); } int main() { scanf("%d%d", &n, &m); for (int i = 1, x, y, w; i <= m; i++) scanf("%d%d%d", &x, &y, &w), edges[i].src = x, edges[i].dst = y, edges[i].weight = w; sort(edges + 1, edges + 1 + m); for (int i = 1; i <= m; i++) edges[i].id = n + i; long long answer = INF, lbound = 1, block_num = 0; for (int i = 1; i <= m; i++) { if (edges[i].src == edges[i].dst) { book[i] = true; continue; } if (!judge(edges[i].src, edges[i].dst)) link(edges[i].src, edges[i].id), link(edges[i].id, edges[i].dst), block_num++; else { split(edges[i].src, edges[i].dst); int now = idx[edges[i].dst]; book[now - n] = true, splay(now); fa[ch[now][0]] = fa[ch[now][1]] = 0; link(edges[i].src, edges[i].id), link(edges[i].id, edges[i].dst); } while (book[lbound] && lbound <= i) lbound++; if (block_num >= n - 1) answer = min(answer, 1LL * edges[i].weight - 1LL * edges[lbound].weight); } printf("%lld", answer); return 0; }
例题:P2387 [NOI2014]魔法森林
这道题也是用 LCT 维护最小生成树的思路来求解的。考虑先按照\(a_i\)进行排序,然后固定上界\(a_i\)求\(b_i\)最小生成树即可。
// P2387.cpp #include <bits/stdc++.h> #define lson ch[p][0] #define rson ch[p][1] using namespace std; const int MAX_N = 2e5 + 200; struct edge { int src, dst, ai, bi; bool operator<(const edge &edg) const { return ai < edg.ai || (ai == edg.ai && bi < edg.bi); } } edges[MAX_N]; int n, m, ch[MAX_N][2], fa[MAX_N], idx[MAX_N], val[MAX_N], tag[MAX_N]; inline bool isRoot(int p) { return ch[fa[p]][0] != p && ch[fa[p]][1] != p; } inline int check(int p) { return ch[fa[p]][1] == p; } inline void pushup(int p) { idx[p] = p; if (lson && val[idx[lson]] > val[idx[p]]) idx[p] = idx[lson]; if (rson && val[idx[rson]] > val[idx[p]]) idx[p] = idx[rson]; } inline void pushdown(int p) { if (tag[p]) { tag[p] = 0; if (lson) tag[lson] ^= 1, swap(ch[lson][0], ch[lson][1]); if (rson) tag[rson] ^= 1, swap(ch[rson][0], ch[rson][1]); } } inline void rotate(int p) { int y = fa[p], z = fa[y], dir = check(p), w = ch[p][dir ^ 1]; fa[p] = z; if (!isRoot(y)) ch[z][check(y)] = p; ch[y][dir] = w, fa[w] = y; ch[p][dir ^ 1] = y, fa[y] = p; pushup(y), pushup(p), pushup(z); } inline void jump(int p) { if (!isRoot(p)) jump(fa[p]); pushdown(p); } inline void splay(int p) { jump(p); for (int fat = fa[p]; fat = fa[p], !isRoot(p); rotate(p)) if (!isRoot(fat)) rotate(check(fat) == check(p) ? fat : p); } inline void access(int p) { for (int fat = 0; p != 0; fat = p, p = fa[p]) splay(p), ch[p][1] = fat, pushup(p); } inline void makeRoot(int p) { access(p), splay(p); swap(lson, rson), tag[p] ^= 1; } inline int find(int p) { access(p), splay(p); while (lson) p = lson; splay(p); return p; } inline void link(int x, int y) { makeRoot(x); fa[x] = y; } inline void split(int x, int y) { makeRoot(x); access(y), splay(y); } inline bool check(int x, int y) { makeRoot(x); return find(y) == x; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) scanf("%d%d%d%d", &edges[i].src, &edges[i].dst, &edges[i].ai, &edges[i].bi); int ans = 2e9; sort(edges + 1, edges + 1 + m); for (int i = 1; i <= m; i++) { int id = i + n; val[id] = edges[i].bi; if (edges[i].src == edges[i].dst) continue; if (!check(edges[i].src, edges[i].dst)) link(edges[i].src, id), link(id, edges[i].dst); else { split(edges[i].src, edges[i].dst); int now = idx[edges[i].dst], maxb = val[now]; if (maxb <= edges[i].bi) continue; splay(now), fa[ch[now][0]] = fa[ch[now][1]] = 0; link(edges[i].src, id), link(id, edges[i].dst); } if (check(1, n)) split(1, n), ans = min(ans, edges[i].ai + val[idx[n]]); } if (ans < 2e9) printf("%d", ans); else puts("-1"); return 0; } // P2387.cpp