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);
- }
注意判断的三个条件,确定存在边
- 联通,将
x " role="presentation" style="position: relative;"> 设置为根。 splay
中父子关系,具体一点y " role="presentation" style="position: relative;"> 是x " role="presentation" style="position: relative;"> 的右儿子x , y " role="presentation" style="position: relative;"> 之间没有点,即y " role="presentation" style="position: relative;"> 没有左儿子
此时体现在 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
- 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
- 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
- #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
- 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");
- }
例 魔法森林
边权由
按照
- #include
- 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
整个辅助树把其他点的并查集祖先修改。并断开标志节点与子树的连接,暴力修改不会超过
n log n " role="presentation" style="position: relative;"> 次
离线显然,查询其实就是(路径上边双个数减一)。
每用一个点用并查集找到它所在边双,例如 access
时
- #include
- 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
- 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);
- }
- }
- }