LCT
LCT
树链剖分
常见的三种:重链、长链、轻实链。LCT
采用的是轻实链剖分。
对于树上每一个点,将某一儿子作为实儿子,注意只有一个
连向其的边设为实边,连向其他子树的边设为虚边。
轻实边需要随着树形态的变化而改变
LCT
支持如下操作:
- 维护数链信息
- 换根
- 联通性
- 动态连边、删边
有了实儿子,还有两个定义:
-
实边,连接父亲和实儿子的边。
-
实链,实边构成的链。
采用 splay
作为 LCT
的辅助树,有如下定义和性质
-
一条实链的辅助树是维护这条链上节点的
splay
每一颗
splay
维护一条从上到下深度严格递增的路径 -
按照节点原树中的深度排序,可得中序遍历
splay
得到的点深度严格递增。 -
一个节点仅在一棵
splay
-
虚边总是由一棵
splay
指向另一个节点重要性质:认父不认子。
即对于一个点,不会在
splay
中记录它的虚儿子,但是会在splay
中记录它虚儿子的父亲为这个点
实现
平衡树
实现其实是实现一个 splay
森林,支持区间翻转操作,点中编号就是原树的编号
因为多个实链多个根,用函数 is_root
判断是否为根。(不过我习惯用 not_root
)
inline int nRt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
splay
函数需要先将点 x
到根路径上的点从上到下 pushdown
一下,因为这里没有查询 kth 的操作。
注意 pushup
pushdown
也有讲究:当一个点打标记时,需保证左右儿子都在正确的位置上。
inline void Up(int x) {
// push up
}
inline void rev(int x) { swap(ch[x][0], ch[x][1]), tg[x] ^= 1; }
inline void down(int x) { if (tg[x]) rev(ch[x][0]), rev(ch[x][1]), tg[x] = 0; }
inline int get(int x) { return ch[fa[x]][1] == x; }
inline int nRt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
inline void rot(int x) {
int y = fa[x], z = fa[y], k = get(x);
if (nRt(y)) ch[z][get(y)] = x; // important
fa[x] = z;
ch[y][k] = ch[x][k ^ 1], fa[ch[x][k ^ 1]] = y;
ch[x][k ^ 1] = y, fa[y] = x;
Up(y), Up(x);
}
inline void upd(int x) {
st[top = 1] = x;
while (nRt(x)) st[++top] = x = fa[x];
while (top) down(st[top--]);
}
inline void splay(int x) {
upd(x); // important
for (int y; y = fa[x], nRt(x); rot(x))
if (nRt(y)) rot(get(x) ^ get(y) ? x : y);
}
access
LCT
的核心操作, access(x)
将 到原树根路径上的边全部变成实边。
并且 为这一条实链的最后一个点,深度最大。
即 与原先实儿子切断, 到原树根路径上所有点构成一个 splay
,便于操作、查询
inline void access(int x) {
for (int y = 0; x; x = fa[y = x])
splay(x), ch[x][1] = y, Up(x);
}
做法:不断将 旋转到当前 splay
的根,它的右儿子就是重儿子,需要修改。用变量 辅助
注意: access
后 不一定为所得 splay
的根
makeroot
将 设为原树树根。
inline void make(int x) {
access(x), splay(x), rev(x);
}
access + splay
后 转到了 splay
的根。
此时 的深度最大,打上翻转标记后深度就最小了,成为了根
findroot
找到 在原树中的根
inline int Find(int x) {
access(x), splay(x), down(x);
while (ch[x][0]) x = ch[x][0], down(x);
splay(x); // remember
return x;
}
同理, access + splay
后 深度最大,在 splay
的根节点。
一直往左儿子走找到深度最小的点就是根节点。
注意最后的 splay
,将原树根转回 splay
的根。等下会提到
split
split(x, y)
得到原树中 到 路径上的点组成的 splay
,并且 为根
inline void split(int x, int y) {
make(x), access(y), splay(y);
}
先将 设为根,然后 access + splay
得到 到 的路径
link
连一条 的轻边。
inline void link(int x, int y) {
make(x);
if (Find(y) ^ x) fa[x] = y;
}
cut
断掉边
inline void cut(int x, int y) {
make(x);
if (Find(y) == x && fa[y] == x && !ch[y][0])
fa[y] = ch[x][1] = 0, Up(x);
}
注意判断的三个条件,确定存在边
- 联通,将 设置为根。
splay
中父子关系,具体一点 是 的右儿子- 之间没有点,即 没有左儿子
此时体现在 findroot
中最后 splay
的作用了。将 splay
的根还原,再继续操作
在一些写法中是没有这个操作的,导致后续的两个判断有些差异,单看这一个函数会觉得莫名其妙。
edge
实际上,判断是否存在边 是非常常用的函数。
edge(x, y)
判断同一个树中,是否存在边 ,借助 split(x, y)
实现
inline bool edge(int x, int y) {
split(x, y);
return ch[y][0] == x && !ch[x][1];
}
code 模板
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 100005;
int n, Ti, a[N], xo[N], ch[N][2], fa[N], tg[N], st[N], top;
inline void Up(int x) { xo[x] = xo[ch[x][0]] ^ xo[ch[x][1]] ^ a[x]; }
inline void rev(int x) { swap(ch[x][0], ch[x][1]), tg[x] ^= 1; }
inline void down(int x) { if (tg[x]) rev(ch[x][0]), rev(ch[x][1]), tg[x] = 0; }
inline int get(int x) { return ch[fa[x]][1] == x; }
inline int nRt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
inline void rot(int x) {
int y = fa[x], z = fa[y], k = get(x);
if (nRt(y)) ch[z][get(y)] = x;
fa[x] = z;
ch[y][k] = ch[x][k ^ 1], fa[ch[x][k ^ 1]] = y;
ch[x][k ^ 1] = y, fa[y] = x;
Up(y), Up(x);
}
inline void upd(int x) {
st[top = 1] = x;
while (nRt(x)) st[++top] = x = fa[x];
while (top) down(st[top--]);
}
inline void splay(int x) {
upd(x);
for (int y; y = fa[x], nRt(x); rot(x))
if (nRt(y)) rot(get(x) ^ get(y) ? x : y);
}
inline void access(int x) {
for (int y = 0; x; x = fa[y = x])
splay(x), ch[x][1] = y, Up(x);
}
inline void make(int x) {
access(x), splay(x), rev(x);
}
inline int Find(int x) {
access(x), splay(x), down(x);
while (ch[x][0]) x = ch[x][0], down(x);
splay(x);
return x;
}
inline void split(int x, int y) {
make(x), access(y), splay(y);
}
inline void link(int x, int y) {
make(x);
if (Find(y) ^ x) fa[x] = y;
}
inline void cut(int x, int y) {
make(x);
if (Find(y) == x && fa[y] == x && !ch[y][0])
fa[y] = ch[x][1] = 0, Up(x);
}
int main() {
scanf("%d%d", &n, &Ti);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), xo[i] = a[i];
for (int op, x, y; Ti--; ) {
scanf("%d%d%d", &op, &x, &y);
if (op == 0) split(x, y), printf("%d\n", xo[y]);
else if (op == 1) link(x, y);
else if (op == 2) cut(x, y);
else if (op == 3) splay(x), a[x] = y, Up(x);
}
}
应用
维护联通性
比较简单。直接用 findroot
判断。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 10005;
char buf[100000], *S = buf, *T = buf;
inline char G() {
return S == T && (T = (S = buf) + fread(buf, 1, 100000, stdin), S == T) ? EOF : *S++;
}
inline int Rd() {
register int x = 0;
char C = G();
for (; C < '0' || C > '9'; C = G()) ;
for (; C > '/' && C < ':'; C = G()) x = (x << 1) + (x << 3) + (C ^ 48);
return x;
}
int n, TT;
int st[N], top;
int fa[N], ch[N][2], tg[N];
char op;
/*
LCT here
*/
int main() {
n = Rd(), TT = Rd();
for (int x, y; TT--; ) {
op = G();
while (op < 'A' || op > 'Z') op = G();
x = Rd(), y = Rd();
if (op == 'C') link(x, y);
else if (op == 'D') cut(x, y);
else puts(Find(x) == Find(y) ? "Yes" : "No");
}
}
维护树链信息
比较简单,用 split
结合各种标记维护。
注意懒标记优先级。由于题目保证联通,link,cut
可以不判断联通性
同时这是国集的题,数据很强,如果硬要在判断联通性,一定不能打假,否则可能调一上午。。。
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 100005;
const LL P = 51061;
int n, T, ch[N][2], fa[N], st[N], top;
LL val[N], sm[N], tg[N], ad[N], mu[N], sz[N];
char op[5];
inline int get(int x) { return ch[fa[x]][1] == x; }
inline int nRt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
inline void Up(int x) {
sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1;
sm[x] = (sm[ch[x][0]] + sm[ch[x][1]] + val[x]) % P;
}
inline void rev(int x) { swap(ch[x][0], ch[x][1]), tg[x] ^= 1; }
inline void mul(int x, LL v) {
(mu[x] *= v) %= P;
(ad[x] *= v) %= P;
(sm[x] *= v) %= P;
(val[x] *= v) %= P;
}
inline void add(int x, LL v) {
(ad[x] += v) %= P;
(sm[x] += v * sz[x]) %= P;
(val[x] += v) %= P;
}
inline void down(int x) {
if (mu[x] != 1) mul(ch[x][0], mu[x]), mul(ch[x][1], mu[x]), mu[x] = 1;
if (ad[x]) add(ch[x][0], ad[x]), add(ch[x][1], ad[x]), ad[x] = 0;
if (tg[x]) {
if (ch[x][0]) rev(ch[x][0]);
if (ch[x][1]) rev(ch[x][1]);
tg[x] = 0;
}
}
inline void rot(int x) {
int y = fa[x], z = fa[y], k = get(x);
if (nRt(y)) ch[z][get(y)] = x;
fa[x] = z;
if (ch[x][k ^ 1]) fa[ch[x][k ^ 1]] = y;
ch[y][k] = ch[x][k ^ 1];
ch[x][k ^ 1] = y, fa[y] = x;
Up(y), Up(x);
}
inline void upd(int x) {
st[top = 1] = x;
while (nRt(x)) st[++top] = x = fa[x];
while (top) down(st[top--]);
}
inline void splay(int x) {
upd(x);
for (int y; y = fa[x], nRt(x); rot(x))
if (nRt(y)) rot(get(x) ^ get(y) ? x : y);
}
inline void access(int x) { for (int y = 0; x; x = fa[y = x]) splay(x), ch[x][1] = y, Up(x); }
inline void make(int x) { access(x), splay(x), rev(x); }
inline int Find(int x) {
access(x), splay(x), down(x);
while (ch[x][0]) x = ch[x][0], down(x);
splay(x);
return x;
}
inline void link(int x, int y) {
make(x);
if (Find(y) != x) fa[x] = y;
}
inline void cut(int x, int y) {
make(x);
if (Find(y) == x && fa[y] == x && !ch[y][0])
fa[y] = ch[x][1] = 0, Up(x);
}
inline void split(int x, int y) { make(x), access(y), splay(y); }
signed main() {
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
scanf("%lld%lld", &n, &T);
for (int i = 1; i <= n; i++) val[i] = mu[i] = sz[i] = 1;
for (int i = 1, u, v; i < n; i++) {
scanf("%lld%lld", &u, &v);
link(u, v);
}
for (int u, v, w; T--; ) {
scanf("%s%lld%lld", op, &u, &v);
if (op[0] == '*') {
scanf("%lld", &w);
split(u, v);
mul(v, 1ll * w);
} else if (op[0] == '+') {
scanf("%lld", &w);
split(u, v);
add(v, 1ll * w);
} else if (op[0] == '/') {
split(u, v);
printf("%lld\n", sm[v]);
} else if (op[0] == '-') {
cut(u, v);
scanf("%lld%lld", &u, &v);
link(u, v);
}
}
}
维护生成树
重要。
以 最小生成树 为例,用类似 kruskal
的思路就是依次加入边
如果加入边 形成了环,就选出 链上边权最大的一条边
如果满足 就删除边 并加入边 ,否则不加入。
加入所有边后即可得到最小生成树,无需对边权排序
由于 lct
的形态会不断变化,难以直接维护边权。处理方法是拆边。
将边权放在代表边的点权里,代表点的点权设为 0 。这样可以只维护点权,是可以实现的操作。
#include <bits/stdc++.h>
using namespace std;
const int N = 250005;
int n, m, a[N], id[N], mx[N], fa[N], ch[N][2], st[N], top, tg[N], tot, ans, use;
inline void Up(int x) {
mx[x] = a[x], id[x] = x;
if (mx[x] < mx[ch[x][0]]) mx[x] = mx[ch[x][0]], id[x] = id[ch[x][0]];
if (mx[x] < mx[ch[x][1]]) mx[x] = mx[ch[x][1]], id[x] = id[ch[x][1]];
}
/*
LCT here
*/
inline int chk(int x, int y) { return make(x), Find(y) == x; }
int main() {
scanf("%d%d", &n, &m);
tot = n;
for (int i = 1, u, v, w, nw; i <= m; i++) {
++tot;
scanf("%d%d%d", &u, &v, &w), a[tot] = w;
if (!chk(u, v)) link(u, tot), link(v, tot), ans += w, ++use;
else {
split(u, v);
if (mx[v] <= w) continue;
nw = id[v];
ans += w - mx[v], splay(nw);
fa[ch[nw][0]] = fa[ch[nw][1]] = 0;
ch[nw][0] = ch[nw][1] = 0;
link(u, tot), link(v, tot);
}
}
if (use == n - 1) printf("%d", ans);
else puts("orz");
}
例 魔法森林
边权由 表示,要求从 1 到 路径上(( 的最大值)和( 的最大值)之和)最小。
按照 排序,维护 的最小生成树。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 200005;
int n, m, ff[N], val[N], id[N], fa[N], ch[N][2], st[N], tg[N], ans = 2e9;
struct Edge { int u, v, a, b; } e[N];
inline bool cmp(Edge A, Edge B) {
return A.b < B.b;
}
inline int gmx(int x, int y) { return val[x] > val[y] ? x : y; }
inline void Up(int x) { id[x] = gmx(x, gmx(id[ch[x][0]], id[ch[x][1]])); }
inline int get(int x) { return ch[fa[x]][1] == x; }
inline bool nRt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
inline void rev(int x) { tg[x] ^= 1, swap(ch[x][0], ch[x][1]); }
inline void down(int x) { if (tg[x]) rev(ch[x][0]), rev(ch[x][1]), tg[x] = 0; }
inline void rot(int x) {
register int y = fa[x], z = fa[y], k = get(x);
if (nRt(y)) ch[z][get(y)] = x;
fa[x] = z;
ch[y][k] = ch[x][k ^ 1], fa[ch[x][k ^ 1]] = y;
ch[x][k ^ 1] = y, fa[y] = x;
Up(y), Up(x);
}
inline void splay(int x) {
register int top, k = x;
st[top = 1] = k;
while (nRt(k)) st[++top] = k = fa[k];
while (top) down(st[top--]);
for (int y; y = fa[x], nRt(x); rot(x))
if (nRt(y)) rot(get(x) ^ get(y) ? x : y);
}
inline void access(int x) { for (int y = 0; x; x = fa[y = x]) splay(x), ch[x][1] = y, Up(x); }
inline void make(int x) { access(x), splay(x), rev(x); }
inline void split(int x, int y) { make(x), access(y), splay(y); }
int fd(int x) { return ff[x] == x ? x : ff[x] = fd(ff[x]); }
inline int Find(int x) {
access(x), splay(x), down(x);
while (ch[x][0]) down(x = ch[x][0]);
return splay(x), x;
}
inline void link(int x, int y) {
make(x);
if (Find(y) ^ x) fa[x] = y;
}
inline void cut(int x, int y) {
make(x);
if (Find(y) == x && fa[y] == x && ch[x][1] == y)
fa[y] = ch[x][1] = 0, Up(x);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n + m; i++) ff[i] = id[i] = i;
for (int i = 1; i <= m; i++) {
scanf("%d%d%d%d", &e[i].u, &e[i].v, &e[i].a, &e[i].b);
}
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= m; i++) val[i + n] = e[i].a;
for (int i = 1, u, v, p, q, fl, o; i <= m; i++) {
p = fd(u = e[i].u), q = fd(v = e[i].v), fl = 1;
if (u == v) continue;
if (p == q) {
split(u, v), o = id[v];
if (val[o] > e[i].a) cut(e[o - n].u, o), cut(o, e[o - n].v);
else fl = 0;
} else ff[p] = q;
if (fl) link(u, i + n), link(i + n, v);
if (fd(1) == fd(n)) split(1, n), ans = min(ans, e[i].b + val[id[n]]);
}
if (ans == 2e9) puts("-1");
else printf("%d", ans);
}
维护边双
用 LCT
的一个点表示一个边双。外面用一个并查集维护每个点属于哪一个边拴。
加入边时,如果不联通就 link
否则,形成的环构成一个边双,把环缩成一个点,把并查集也合并。
-
暴力缩点:将当前辅助树的根节点设置为集合的标志节点,
dfs
整个辅助树把其他点的并查集祖先修改。并断开标志节点与子树的连接,暴力修改不会超过 次
离线显然,查询其实就是(路径上边双个数减一)。
每用一个点用并查集找到它所在边双,例如 access
时
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 1e5 + 5;
int n, m, Ti, ty[N], ans[N], outs, dl[N], fa[N], ch[N][2], tg[N], sz[N], st[N], ff[N], qu[N], qv[N];
struct edge {
int u, v;
bool operator < (edge A) const {
if (u ^ A.u) return u < A.u;
return v < A.v;
}
} E[N];
int fd(int x) { return ff[x] == x ? x : ff[x] = fd(ff[x]); }
inline void Up(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1; }
inline int get(int x) { return ch[fd(fa[x])][1] == x; }
inline int nRt(int x) { return ch[fd(fa[x])][0] == x || ch[fd(fa[x])][1] == x; }
inline void rev(int x) { tg[x] ^= 1, swap(ch[x][0], ch[x][1]); }
inline void down(int x) { if (tg[x]) rev(ch[x][0]), rev(ch[x][1]), tg[x] = 0; }
inline void rot(int x) {
register int y = fd(fa[x]), z = fd(fa[y]), k = get(x);
if (nRt(y)) ch[z][get(y)] = x;
fa[x] = z;
ch[y][k] = ch[x][k ^ 1], fa[ch[x][k ^ 1]] = y;
ch[x][k ^ 1] = y, fa[y] = x;
Up(y), Up(x);
}
inline void splay(int x) {
register int top, k = x;
st[top = 1] = k;
while (nRt(k)) st[++top] = k = fd(fa[k]);
while (top) down(st[top--]);
for (int y; y = fd(fa[x]), nRt(x); rot(x))
if (nRt(y)) rot(get(x) ^ get(y) ? x : y);
}
inline void access(int x) { for (int y = 0; x; y = x, x = fd(fa[x])) splay(x), ch[x][1] = y, Up(x); }
inline void make(int x) { access(x), splay(x), rev(x); }
inline int Find(int x) {
access(x), splay(x), down(x);
while (ch[x][0]) down(x = ch[x][0]);
return splay(x), x;
}
inline void split(int x, int y) { make(y), access(x), splay(x); }
void del(int x, int y) {
if (x) ff[x] = y, sz[y] += sz[x], del(ch[x][0], y), del(ch[x][1], y);
}
inline void mer(int x, int y) {
if (x == y) return;
make(x);
if (Find(y) ^ x) { fa[x] = y; return; }
del(ch[x][1], x), ch[x][1] = 0, Up(x);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) sz[i] = 1, ff[i] = i;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &E[i].u, &E[i].v);
if (E[i].u > E[i].v) swap(E[i].u, E[i].v);
}
sort(E + 1, E + m + 1);
for (int op, u, v; scanf("%d", &op), op != -1;) {
scanf("%d%d", &u, &v);
if (u > v) swap(u, v);
++Ti, qu[Ti] = u, qv[Ti] = v, ty[Ti] = op;
if (!op) dl[lower_bound(E + 1, E + m + 1, (edge){ u, v }) - E] = 1;
}
for (int i = 1; i <= m; i++)
if (!dl[i]) mer(fd(E[i].u), fd(E[i].v));
for (int x, y; Ti; --Ti) {
x = fd(qu[Ti]), y = fd(qv[Ti]);
if (ty[Ti]) split(x, y), ans[++outs] = sz[x] - 1;
else mer(x, y);
}
while (outs) printf("%d\n", ans[outs--]);
}
维护子树信息
lct
最大痛点,树剖在这里由不可取代的地位。
常见的是维护子树信息总和,如大小、权值。
以 [BJOI2014]大融合 为例
查询就是 cut(x,y)
后两个子树大小的乘积。
用多一个数组 记录虚儿子的信息,注意 为所有儿子的信息,包括实儿子
需要修改 pushup, access, link
就是根据虚实边的转化修改。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
char buf[100000], *S = buf, *T = buf;
inline char G() {
return S == T && (T = (S = buf) + fread(buf, 1, 100000, stdin), S == T) ? EOF : *S++;
}
inline int Rd() {
register int x = 0;
static char C = G();
for (; C < '0' || C > '9'; C = G()) ;
for (; C > '/' && C < ':'; C = G()) x = (x << 1) + (x << 3) + (C ^ 48);
return x;
}
const int N = 100005;
int n, Ti, tg[N], sz[N], sz2[N], fa[N], ch[N][2], st[N], top;
char op;
inline int get(int x) { return ch[fa[x]][1] == x; }
inline int nRt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
inline void Up(int x) { sz[0] = 0, sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1 + sz2[x]; }
inline void rev(int x) { swap(ch[x][0], ch[x][1]), tg[x] ^= 1; }
inline void down(int x) { if (tg[x]) rev(ch[x][0]), rev(ch[x][1]), tg[x] = 0; }
inline void rot(int x) {
int y = fa[x], z = fa[y], k = get(x);
if (nRt(y)) ch[z][get(y)] = x;
fa[x] = z;
ch[y][k] = ch[x][k ^ 1], fa[ch[x][k ^ 1]] = y;时
ch[x][k ^ 1] = y, fa[y] = x;
Up(y), Up(x);
}
inline void upd(int x) {
st[top = 1] = x;
while (nRt(x)) st[++top] = x = fa[x];
while (top) down(st[top--]);
}
inline void splay(int x) {
upd(x);
for (int y; y = fa[x], nRt(x); rot(x))
if (nRt(y)) rot(get(x) ^ get(y) ? x : y);
}
inline void access(int x) {
for (int y = 0; x; x = fa[y = x])
splay(x), sz2[x] += sz[ch[x][1]] - sz[y], ch[x][1] = y, Up(x);
}
inline void make(int x) { access(x), splay(x), rev(x); }
inline int Find(int x) {
access(x), splay(x), down(x);
while (ch[x][0]) down(x = ch[x][0]);
return splay(x), x;
}
inline void link(int x, int y) {
make(x), make(y), fa[x] = y, sz2[y] += sz[x], Up(y);
}
inline void cut(int x, int y) {
make(x), Find(y), fa[y] = ch[x][1] = 0, Up(x);
}
int main() {
n = Rd(), Ti = Rd();
for (int x, y; Ti--; ) {
op = G();
while (op < 'A' || op > 'Z') op = G();
x = Rd(), y = Rd();
if (op == 'A') link(x, y);
else {
cut(x, y), make(x), make(y);
printf("%lld\n", 1ll * sz[x] * sz[y]);
link(x, y);
}
}
}
本文作者:小蒟蒻laf
本文链接:https://www.cnblogs.com/KonjakLAF/p/16581192.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步