这题是bzoj权限题,我没权限号,所以没法交,只好在洛谷上做。
题解:
我们发现,对于A操作,支持连接,首先就会想到LCT,然后还可能是离线并查集之类的东西,但是我们发现似乎并查集并不容易维护这个要求是树的重心的首都,所以考虑用LCT。网上大多数的做法是启发式合并,每次从size小的树找一个点合并到size大的树上,这样每次合并重心最多移动一次,这样复杂度应该是nlog^2n(n*logn*logn)。但是我在网上学到了一个复杂度更优的做法。我们发现,上面的做法的复杂度瓶颈在于启发式合并时一个一个地加点,那么我们考虑是否可以直接把两棵树合并呢?答案是可以的。根据重心的一些性质,我们可以发现合并前后,新的重心应该会出现在两棵树连通后原来的两个首都之间的路径上。那么我们
合并之后用LCT把这条路径提取出来,只需要在这条路径上找即可。然后写法上是一种类似二分的写法,也是一个要维护虚子树大小的题,维护方法与大融合那道题相同。
再看Q操作,相当于找一个连通块的根,这个LCT和并查集都可以,但是显然用并查集维护会常数小。
对于Xor操作,我们只需要一开始预处理出n个数的异或和,每连接两个点,就把它们原来的首都的编号异或掉(同一个数被异或两边相当于对答案没有贡献,一开始异或过一次,这里再异或一次就相当于去掉了),再异或上新的首都。
下面是代码,重点就是update函数。
#include <bits/stdc++.h> using namespace std; int n,m,f[100010],c[100010][2],fa[100010],rev[100010],st[100010]; int res,s[100010],si[100010]; inline int getr(int x) { if(x==fa[x]) return x; else { fa[x]=getr(fa[x]); return fa[x]; } } inline void pushup(int x) { s[x]=s[c[x][0]]+s[c[x][1]]+si[x]+1; } inline void pushdown(int x) { if(rev[x]) { swap(c[x][0],c[x][1]); rev[c[x][0]]^=1; rev[c[x][1]]^=1; rev[x]=0; } } inline int nroot(int x) { return c[f[x]][0]==x||c[f[x]][1]==x; } inline void rotate(int x) { int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k]; if(nroot(y)) c[z][c[z][1]==y]=x; c[x][!k]=y; c[y][k]=w; if(w) f[w]=y; f[y]=x; f[x]=z; pushup(y); } inline void splay(int x) { int y=x,z=0; st[++z]=y; while(nroot(y)) { y=f[y]; st[++z]=y; } while(z) pushdown(st[z--]); while(nroot(x)) { y=f[x],z=f[y]; if(nroot(y)) { if(c[z][0]==y ^ c[y][0]==x) rotate(x); else rotate(y); } rotate(x); } pushup(x); } inline void access(int x) { int y=0; while(x!=0) { splay(x); si[x]+=s[c[x][1]]-s[y]; c[x][1]=y; y=x; x=f[x]; } } inline void makeroot(int x) { access(x); splay(x); rev[x]^=1; } inline void split(int x,int y) { makeroot(x); access(y); splay(y); } inline void link(int x,int y) { makeroot(x); access(y); splay(y); f[x]=y; si[y]+=s[x]; pushup(y); } inline int update(int x) { int l,r,ji=s[x]&1,sum=s[x]>>1,lsum=0,rsum=0,nowl,nowr,now=2e9; while(x) { pushdown(x);//注意pushdown l=c[x][0]; r=c[x][1]; nowl=s[l]+lsum; nowr=s[r]+rsum; if(nowl<=sum&&nowr<=sum) { if(ji)//点数为奇数只有一个重心 { now=x; break; } else if(now>x)//选编号小的 now=x; } if(nowl<nowr) { lsum+=s[l]+si[x]+1; x=r; } else { rsum+=s[r]+si[x]+1; x=l; } } splay(now); return now; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) { fa[i]=i; s[i]=1; res^=i; } char opt[10]; int x,y,z; for(int i=1;i<=m;++i) { scanf("%s",opt); if(opt[0]=='A') { scanf("%d%d",&x,&y); link(x,y); x=getr(x); y=getr(y); split(x,y); z=update(y); res=res^x^y^z; fa[x]=fa[y]=fa[z]=z; //去掉原来的两个首都编号,异或上新的首都编号 } else if(opt[0]=='Q') { scanf("%d",&x); printf("%d\n",getr(x)); } else printf("%d\n",res); } return 0; }