这题是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;
}