bzoj3510: 首都
题面:在X星球上有N个国家,每个国家占据着X星球的一座城市。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。
X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。
现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理:
1、A x y:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。
2、Q x:询问当前编号为x的城市所在国家的首都。
X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。
现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理:
1、A x y:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。
2、Q x:询问当前编号为x的城市所在国家的首都。
3、Xor:询问当前所有国家首都编号的异或和。
思路:动态树维护重心,保证每个国家的树根是重心以回答第二个问题,重要的是合并操作。对于合并操作,暴力拆小树,一个一个接到大树上,因为只会合并,所以所有操作总共只会接O(n)个点。接完后,把小树重心到大树重心的路径access,新树的重心一定在这条路径上,我们只要把重心即根向小树方向暴力移动即可,移动时要记得更新size和第3问的答案。这样暴力移动只会移动不超过小树大小步,所以所有操作总共只会移动O(n)次,总复杂度就有保证了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ls c[x][0] #define rs c[x][1] using namespace std; const int maxn=100010,maxm=400010; int pre[maxm],now[maxn],son[maxm],tot,n,m,vis[maxn],q[maxn<<1],ans;char op[5],ch; void ins(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;} void read(int &x){ for (ch=getchar();!isdigit(ch);ch=getchar()); for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; } struct LCT{ int size[maxn],c[maxn][2],fa[maxn],add[maxn]; bool isroot(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;} int which(int x){return c[fa[x]][1]==x;} void update(int x){} void inc(int x,int v){size[x]+=v,add[x]+=v;} void down(int x){if (add[x]) inc(ls,add[x]),inc(rs,add[x]),add[x]=0;} void relax(int x){if (!isroot(x)) relax(fa[x]);down(x);} void rotate(int x){ int y=fa[x],z=fa[y],nx=which(x),ny=which(y); fa[c[x][!nx]]=y,c[y][nx]=c[x][!nx]; fa[x]=z;if (!isroot(y)) c[z][ny]=x; fa[y]=x,c[x][!nx]=y;update(y); } void splay(int x){ relax(x); while (!isroot(x)){ if (isroot(fa[x])) rotate(x); else if (which(x)==which(fa[x])) rotate(fa[x]),rotate(x); else rotate(x),rotate(x); } update(x); } void access(int x){for (int p=0;x;p=x,x=fa[x]) splay(x),fa[c[x][1]=p]=x,update(x);} int findroot(int x){ access(x),splay(x); for (;ls;x=ls) down(x); return x; } int next(int x){ x=rs;for (;ls;x=ls); if (x) splay(x);return x; } void bfs(int x,int f){//拆完小树接到大树上,更新新树的size int tail=1;fa[q[1]=x]=f,size[x]=1,ls=rs=add[x]=0,vis[x]=m;//小树暴力重构了,连边的点x没有fa了,所以不用makeroot(x); for (int head=1;head<=tail;head++) for (int y=now[x=q[head]];y;y=pre[y]) if (vis[son[y]]!=m) fa[q[++tail]=son[y]]=x,c[son[y]][0]=c[son[y]][1]=add[son[y]]=0,size[son[y]]=1,vis[son[y]]=m; for (int i=tail;i;i--) x=q[i],size[fa[x]]+=size[x]; } void merge(int x,int y){ int fx=findroot(x),fy=findroot(y); if (fx==fy) return; if (size[fx]<size[fy]) swap(x,y),swap(fx,fy);//把小的合并到大的 ans^=fy,bfs(y,x),ins(x,y),ins(y,x);//去除小树对答案的影响 access(x),splay(x),inc(ls,size[y]),access(fy),splay(fx);//x到大树的根的路径上的点size都要加上小树的size for (;;){//寻找新树的重心,保证每棵树的根的是重心。从原重心向刚接起的小树方向不断移动,直到找到新重心。 int z=next(fx);if (!z) break; if (size[z]*2>size[fx]||(size[z]*2==size[fx]&&z<fx)){ c[z][0]=0,swap(size[z],size[fx]),size[fx]=size[z]-size[fx]; //把新找到更靠近重心的点设为根 //于是没有比它深度更浅的点,更新size ans^=z^fx,fx=z;//更新答案 } else break; } } }T; int main(){ scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) T.size[i]=1,ans^=i; while (m--){ scanf("%s",op);int x,y; if (op[0]=='X') printf("%d\n",ans); else if (op[0]=='Q') read(x),printf("%d\n",T.findroot(x)); else read(x),read(y),T.merge(x,y); } return 0; }