problem
solution
本题考察了很经典的模型,运用了很经典的解法。
本题用到了重心的两个性质:
-
两棵树合并为同一棵树时,新的重心一定在原来两棵树各自重心的路径上。
-
重心为根时的最大子树大小最小,不超过 s i z / 2 siz/2 siz/2。
如果树节点个数为奇数,则只有一个重心。否则有两个重心。
找到 x , y x,y x,y 各自所在的国家的重心 g x , g y gx,gy gx,gy。
将 x , y x,y x,y 连边完成国家合并。
然后把 g x − g y gx-gy gx−gy 的路径 split \text{split} split 出来。
接着就暴力 dfs \text{dfs} dfs 根据左右儿子的 siz \text{siz} siz 判断走左还是走右,因为每次 s i z siz siz 至少减小一半,所以时间复杂度仍然是一个 l o g log log。
具体而言:当左右子树 siz \text{siz} siz 都不超过总个数的一半时,将这个点纳入重心备选。然后看左右儿子谁的 s i z siz siz 更大,就往哪边走。
但是很遗憾 LCT \text{LCT} LCT 认父不认子,对于一个点只能记录两个左右儿子的 siz \text{siz} siz。
我们好像不能获得一个点的子树内所有的节点个数?
这就是一个 LCT \text{LCT} LCT 经典应用了。
用 t [ i ] . s i z t[i].siz t[i].siz 记录 i i i 子树大小, t [ i ] . t o t t[i].tot t[i].tot 记录除开两个左右儿子的 s i z siz siz,其余虚儿子的 s i z siz siz 的和。
更新就需要写成:
t[x].siz = t[t[x].son[0]].siz + t[t[x].son[1]].siz + 1 + t[x].tot;
与此同时,所有涉及更改虚实儿子的,都要重新维护 t o t tot tot。
一般就是 access \text{access} access 中的操作,会多加一行。
t[x].tot += t[t[x].son[1]].siz - t[son].siz;
而且为了保证信息的时效性,我们的 link \text{link} link 操作要还是像以前一样,直接记一下 x x x 的父亲是 y y y 就不行了。
也不能直接把 y y y 的信息更新一下就完了。
因为一旦 x , y x,y x,y 连边,各自所在的子树的位置一路往上信息都要修改。
所以我们不如直接让 x , y x,y x,y 成为联通块的根, split(x,y) \text{split(x,y)} split(x,y) 即可。
由于 L C T LCT LCT 的 findroot \text{findroot} findroot 慢得慌,我们直接用并查集维护每个联通块的重心即可,重心的异或和也用个变量维护即可。
具体可见代码实现。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
namespace LCT {
int top = 0, sta[maxn];
struct node { int son[2], fa, tot, tag, siz; }t[maxn];
bool root( int x ) { return t[t[x].fa].son[0] ^ x and t[t[x].fa].son[1] ^ x; }
void pushup( int x ) { t[x].siz = t[t[x].son[0]].siz + t[t[x].son[1]].siz + 1 + t[x].tot; }
void reverse( int x ) { swap( t[x].son[0], t[x].son[1] ); t[x].tag ^= 1; }
void pushdown( int x ) {
if( ! t[x].tag ) return;
if( t[x].son[0] ) reverse( t[x].son[0] );
if( t[x].son[1] ) reverse( t[x].son[1] );
t[x].tag = 0;
}
void rotate( int x ) {
int fa = t[x].fa;
int Gfa = t[fa].fa;
int d = t[fa].son[1] == x;
if( ! root( fa ) ) t[Gfa].son[t[Gfa].son[1] == fa] = x;
t[x].fa = Gfa;
if( t[x].son[d ^ 1] ) t[t[x].son[d ^ 1]].fa = fa;
t[fa].son[d] = t[x].son[d ^ 1];
t[x].son[d ^ 1] = fa;
t[fa].fa = x;
pushup( fa );
pushup( x );
}
void splay( int x ) {
sta[++ top] = x; int y = x;
while( ! root( y ) ) sta[++ top] = y = t[y].fa;
while( top ) pushdown( sta[top --] );
while( ! root( x ) ) {
int fa = t[x].fa, Gfa = t[fa].fa;
if( ! root( fa ) ) (t[Gfa].son[1] == fa) ^ (t[fa].son[1] == x) ? rotate( x ) : rotate( fa );
rotate( x );
}
}
void access( int x ) {
for( int son = 0;x;son = x, x = t[x].fa ) {
splay( x );
t[x].tot += t[t[x].son[1]].siz - t[son].siz;
t[x].son[1] = son;
pushup( x );
}
}
void makeroot( int x ) { access( x ); splay( x ); reverse( x ); }
void split( int x, int y ) { makeroot( x ); access( y ); splay( y ); }
void link( int x, int y ) { split( x, y ); t[t[x].fa = y].tot += t[x].siz; pushup( y ); }
int dfs( int x ) {
int lsum = 0, rsum = 0, siz = t[x].siz >> 1, flag = t[x].siz & 1, ans = 1e9;
while( x ) {
pushdown( x );
int lcnt = lsum + t[t[x].son[0]].siz;
int rcnt = rsum + t[t[x].son[1]].siz;
if( lcnt <= siz and rcnt <= siz ) {
if( flag ) { ans = x; break; }
else ans = min( ans, x );
}
if( lcnt < rcnt ) {
lsum += t[x].siz - t[t[x].son[1]].siz; // + t[t[x].son[0]].siz + t[x].tot + 1
x = t[x].son[1];
}
else {
rsum += t[x].siz - t[t[x].son[0]].siz; // +t[t[x].son[1]].siz + t[x].tot + 1
x = t[x].son[0];
}
}
splay( ans );
return ans;
}
}
int f[maxn];
int find( int x ) { return x == f[x] ? x : f[x] = find( f[x] ); }
int main() {
int ans = 0, x, y, n, m; char opt[10];
scanf( "%d %d", &n, &m );
iota( f + 1, f + n + 1, 1 );
for( int i = 1;i <= n;i ++ ) ans ^= i;
for( int i = 1;i <= m;i ++ ) {
scanf( "%s", opt );
switch( opt[0] ) {
case 'X' : printf( "%d\n", ans ); break;
case 'Q' : {
scanf( "%d", &x );
printf( "%d\n", find( x ) );
break;
}
case 'A' : {
scanf( "%d %d", &x, &y );
LCT :: link( x, y );
x = find( x ), y = find( y );
LCT :: split( x, y );
int g = LCT :: dfs( y );
ans ^= x ^ y ^ g;
f[x] = f[y] = f[g] = g;
break;
}
}
}
return 0;
}