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的城市所在国家的首都。 

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;
}



posted @ 2015-06-25 10:16  orzpps  阅读(163)  评论(0编辑  收藏  举报