problem
luogu-P4299
solution
本题考察了很经典的模型,运用了很经典的解法。
本题用到了重心的两个性质:
-
两棵树合并为同一棵树时,新的重心一定在原来两棵树各自重心的路径上。
-
重心为根时的最大子树大小最小,不超过 siz/2siz/2siz/2。
如果树节点个数为奇数,则只有一个重心。否则有两个重心。
找到 x,yx,yx,y 各自所在的国家的重心 gx,gygx,gygx,gy。
将 x,yx,yx,y 连边完成国家合并。
然后把 gx−gygx-gygx−gy 的路径 split\text{split}split 出来。
接着就暴力 dfs\text{dfs}dfs 根据左右儿子的 siz\text{siz}siz 判断走左还是走右,因为每次 sizsizsiz 至少减小一半,所以时间复杂度仍然是一个 logloglog。
具体而言:当左右子树 siz\text{siz}siz 都不超过总个数的一半时,将这个点纳入重心备选。然后看左右儿子谁的 sizsizsiz 更大,就往哪边走。
但是很遗憾 LCT\text{LCT}LCT 认父不认子,对于一个点只能记录两个左右儿子的 siz\text{siz}siz。
我们好像不能获得一个点的子树内所有的节点个数?
这就是一个 LCT\text{LCT}LCT 经典应用了。
用 t[i].sizt[i].sizt[i].siz 记录 iii 子树大小,t[i].tott[i].tott[i].tot 记录除开两个左右儿子的 sizsizsiz,其余虚儿子的 sizsizsiz 的和。
更新就需要写成:
t[x].siz = t[t[x].son[0]].siz + t[t[x].son[1]].siz + 1 + t[x].tot;
与此同时,所有涉及更改虚实儿子的,都要重新维护 tottottot。
一般就是 access\text{access}access 中的操作,会多加一行。
t[x].tot += t[t[x].son[1]].siz - t[son].siz;
而且为了保证信息的时效性,我们的 link\text{link}link 操作要还是像以前一样,直接记一下 xxx 的父亲是 yyy 就不行了。
也不能直接把 yyy 的信息更新一下就完了。
因为一旦 x,yx,yx,y 连边,各自所在的子树的位置一路往上信息都要修改。
所以我们不如直接让 x,yx,yx,y 成为联通块的根,split(x,y)\text{split(x,y)}split(x,y) 即可。
由于 LCTLCTLCT 的 findroot\text{findroot}findroot 慢得慌,我们直接用并查集维护每个联通块的重心即可,重心的异或和也用个变量维护即可。
具体可见代码实现。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005namespace 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 + 1x = t[x].son[1];}else {rsum += t[x].siz - t[t[x].son[0]].siz; // +t[t[x].son[1]].siz + t[x].tot + 1x = 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;
}