bzoj2733 永无乡 线段树合并

       这道题是一道经典的平衡树+启发式合并吧。那么考虑用可持久化线段树来写。

       对每一个节点保存一棵线段树表示所在块的编号的集合(因此可以一个块值保存一棵树),然后合并的时候就地柜合并左子节点和右子节点,然后更新节点的值即可。显然这样和启发式合并时等价的。因此为O(Nlog^2N),只是空间大了一点为O(NlogN),平衡树O(N)。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100005
#define M 2000005
using namespace std;

int n,m,trtot,a[N],id[N],fa[N],rt[N],sum[M],ls[M],rs[M];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
void ins(int &k,int l,int r,int v){
	if (!k) k=++trtot; if (l==r){ sum[k]=1; return; }
	int mid=(l+r)>>1;
	if (v<=mid) ins(ls[k],l,mid,v); else ins(rs[k],mid+1,r,v);
	sum[k]=sum[ls[k]]+sum[rs[k]];
}
int merge(int x,int y){
	if (!x || !y) return x+y;
	ls[x]=merge(ls[x],ls[y]); rs[x]=merge(rs[x],rs[y]);
	sum[x]=sum[ls[x]]+sum[rs[x]]; return x;
}
int qry(int k,int rst){
	int l=1,r=n; if (sum[k]<rst) return -1;
	while (l<r){
		int tmp=sum[ls[k]],mid=(l+r)>>1;
		if (tmp>=rst){ k=ls[k]; r=mid; }
		else{ k=rs[k]; l=mid+1; rst-=tmp; }
	}
	return id[l];
}
int getfa(int x){ return (x==fa[x])?x:fa[x]=getfa(fa[x]); }
int main(){
	n=read(); m=read(); int i,x,y;
	for (i=1; i<=n; i++){
		a[i]=read(); fa[i]=i; id[a[i]]=i;
	}
	while (m--){
		x=getfa(read()),y=getfa(read());
		if (x!=y) fa[x]=y;
	}
	for (i=1; i<=n; i++) ins(rt[getfa(i)],1,n,a[i]);
	m=read(); char ch;
	while (m--){
		ch=getchar(); while (ch<'A' || ch>'Z') ch=getchar();
		x=read(); y=read();
		if (ch=='B'){
			x=getfa(x); y=getfa(y);
			if (x!=y){ fa[x]=y; rt[y]=merge(rt[x],rt[y]); }
		} else printf("%d\n",qry(rt[getfa(x)],y));
	}
	return 0;
}


by lych

2016.3.10

你可能感兴趣的:(线段树,启发式合并)