【题解】 首都 LCT维护子树信息+二分 Luogu4299
Legend
初始有 \(n\ (1 \le n \le 10^5)\) 个孤立点,支持以下操作 \(m\ (1 \le m \le 2\times 10^5)\) 次:
- 连边 \((u,v)\),保证图中无环;
- 询问 \(u\) 所在树的重心;
- 求所有树的中心异或和;
当有多个重心时,取编号较小的那个。
Editorial
已知两棵树的重心,现在连一条边把它们连起来,有一个结论:
Lemma1:新树的重心位于连接原本重心的路径上。
证明显然,读者自证不难。
那么现在的问题就是怎么快速求这个重心的位置?显然把这条路径 split 出来二分就行了。
那么在 LCT 上怎么二分呢?
LCT 是可以维护子树 size 的,所以可以直接做;
你可能会问:不是只有根部的 size 信息才是真实的吗?我二分是在 splay 上二分的,肯定不能改变树的形态呀!
事实上我们并不需要把当前这个点转到根,LCT 维护的是一条链,你可以在二分的过程中顺带求出对于当前二分点深度较深的一部分的子树大小。
特别地,当出现两个重心的情况 \((2size_u = n)\),还要检查二分到的重心的 parent 是否编号比较小。
Code
有一个叫做 search
的函数非常的重要。
#include <bits/stdc++.h>
#define debug(...) ;//fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
freopen(#x".in" ,"r" ,stdin);\
freopen(#x".out" ,"w" ,stdout)
#define LL long long
using namespace std;
const int MX = 1e5 + 23;
const LL MOD = 998244353;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
struct LCT{
#define lch(x) ch[x][0]
#define rch(x) ch[x][1]
int ch[MX][2] ,fa[MX] ,size[MX] ,Vsz[MX] ,rev[MX];
LCT(){for(int i = 1 ; i < MX ; ++i) size[i] = 1;}
int get(int x){return x == rch(fa[x]);}
int Nroot(int x){return get(x) || x == lch(fa[x]);}
void pushup(int x){size[x] = size[lch(x)] + size[rch(x)] + Vsz[x] + 1;}
void dorev(int x){swap(lch(x) ,rch(x)); rev[x] ^= 1;}
void pushdown(int x){if(rev[x]) dorev(lch(x)) ,dorev(rch(x)) ,rev[x] = 0;}
void rotate(int x){
int f = fa[x] ,gf = fa[f] ,which = get(x) ,W = ch[x][!which];
if(Nroot(f)) ch[gf][get(f)] = x;
ch[x][!which] = f ,ch[f][which] = W;
if(W) fa[W] = f;
fa[f] = x ,fa[x] = gf ,pushup(f);
}
int stk[MX] ,dep;
void splay(int x){
int f = x; stk[++dep] = f;
while(Nroot(f)) stk[++dep] = f = fa[f];
while(dep) pushdown(stk[dep--]);
while(Nroot(x)){
if(Nroot(f = fa[x])) rotate(get(x) == get(f) ? f : x);
rotate(x);
}pushup(x);
}
void access(int x){
for(int y = 0 ; x ; x = fa[y = x]){
splay(x);
Vsz[x] += size[rch(x)];
Vsz[x] -= size[rch(x) = y];
pushup(x);
}
}
void makeroot(int x){access(x) ,splay(x) ,dorev(x);}
void split(int x ,int y){makeroot(x) ,access(y) ,splay(x);}
void link(int x ,int y){
makeroot(x) ,access(y) ,splay(y);
fa[x] = y;
Vsz[y] += size[x];
}
int getpre(int x){
splay(x);
pushdown(x);
x = lch(x);
while(rch(x)) pushdown(x) ,x = rch(x);
return x;
}
int search(int x ,int allsz){
int ans = 0 ,up = 0 ,ans2 = 0;
int szans = INT_MAX ,szans2 = INT_MAX;
while(x){
pushdown(x);
int sznow = up + 1 + Vsz[x] + size[rch(x)];
int ok = 0;
if(sznow * 2 >= allsz){
ok = 1;
if(sznow < szans2){
ans2 = x;
szans2 = sznow;
}
if(szans2 < szans){
swap(szans2 ,szans);
swap(ans2 ,ans);
}
}
int szr = up + 1 + Vsz[rch(x)] + size[rch(rch(x))];
if(!lch(x) || (ok && rch(x))){
x = rch(x);
}
else{
up += 1 + size[rch(x)] + Vsz[x];
x = lch(x);
}
}
if(szans * 2 == allsz){
int x = getpre(ans);
if(x && x < ans) return x;
}
return ans;
}
#undef lch
#undef rch
}T;
int fa[MX] ,cap[MX] ,sz[MX];
void init(){for(int i = 1 ; i < MX ; ++i) fa[i] = i ,sz[i] = 1;}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
void link(int x ,int y){
x = find(x) ,y = find(y);
if(x == y) return;
fa[x] = y ,sz[y] += sz[x];
}
void solve(){
int n = read() ,m = read();
init();
int con = 0;
for(int i = 1 ; i <= n ; ++i){
con ^= i;
cap[i] = i;
}
for(int i = 1 ; i <= m ; ++i){
char str[33];
scanf("%s" ,str);
if(str[0] == 'A'){
int x = read() ,y = read();
if(x == 5 && y == 10){
debug("ASDFDASDF");
}
con ^= cap[find(x)] ^ cap[find(y)];
T.link(x ,y);
T.split(cap[find(x)] ,cap[find(y)]);
int c = T.search(cap[find(x)] ,sz[find(x)] + sz[find(y)]);
link(x ,y);
cap[find(x)] = c;
debug("NEW CAP of(%d %d) = %d\n" ,x ,y ,c);
con ^= cap[find(x)];
}
if(str[0] == 'Q'){
printf("%d\n" ,cap[find(read())]);
}
if(str[0] == 'X'){
printf("%d\n" ,con);
}
}
}
int main(){
int T = 1;
for(int i = 1 ; i <= T ; ++i){
solve();
}
return 0;
}