rausen loves cakes HDU - 5997线段树合并

Part1

​ 感觉本题与梦幻布丁(题解)十分相似。只不过由全局查找变为区间查找。若是此时对于每种颜色都查询一遍区间内颜色段数复杂度必定超。但由梦幻布丁这道题做铺垫后,可以想到维护一个区间内所有颜色的段数,可以另外拿一个线段树来维护,查询时在这个线段树上回答即可。

Part2

​ 为了快速确定某两个相邻的点是否颜色相同,可以用并查集。把每种颜色的代表点存下来(也就是随便存下一个这种颜色的点的位置),每次合并时将两个代表合并即可。

AC代码:

#include
#include
#include
#define M 100005
using namespace std;
int C[M];
map<int,int>mp;
int fa[M],Fr[M];
int getfa(int v){
	if(fa[v]==v)return v;
	return fa[v]=getfa(fa[v]);
}
struct SegmentTree{
	int Root[M],tot;
	int Lson[M*20],Rson[M*20],cnt[M*20],sum[M*20];//一个log 
	bool Lmark[M*20],Rmark[M*20];
	void Init(){
		tot=0;
		memset(Root,0,sizeof(Root));
		memset(sum,0,sizeof(sum));
	}
	void Clear(int tid){//建一个节点要先清空 
		Lson[tid]=Rson[tid]=0;
		cnt[tid]=0;
		Lmark[tid]=Rmark[tid]=0;
	}
	void Up(int p){
		Lmark[p]=Lmark[Lson[p]];
		Rmark[p]=Rmark[Rson[p]];
		cnt[p]=cnt[Lson[p]]+cnt[Rson[p]]-(Rmark[Lson[p]]&&Lmark[Rson[p]]);
	}
	void Updata(int L,int R,int x,int &tid,int p){//此处的p时线段树节点编号 
		if(!tid)tid=++tot,Clear(tid);
		sum[p]-=cnt[tid];//先减去 
		if(L==R){
			cnt[tid]=1;
			sum[p]++;//注意在此时不要忘了更新sum数组 
			Lmark[tid]=Rmark[tid]=1;
			return;
		}
		int mid=(L+R)>>1;
		if(x<=mid)Updata(L,mid,x,Lson[tid],p<<1);
		else Updata(mid+1,R,x,Rson[tid],p<<1|1);
		Up(tid);
		sum[p]+=cnt[tid];//更新 
	}
	int Merge(int x,int y,int p){
		if(!x)return y;
		if(!y)return x;
		sum[p]-=cnt[y]+cnt[x];//合并时也要维护sum数组 
		Lson[y]=Merge(Lson[x],Lson[y],p<<1);
		Rson[y]=Merge(Rson[x],Rson[y],p<<1|1);
		Up(y);
		sum[p]+=cnt[y];
		return y;
	}
	void Union(int x,int y){
		Root[y]=Merge(Root[x],Root[y],1);
	}
	int Query(int L,int R,int Lx,int Rx,int p){//类似在线段树上查询 
		if(Lx>Rx)return 0;
		if(Lx<=L&&R<=Rx)return sum[p];
		int mid=(L+R)>>1;
		if(Rx<=mid)return Query(L,mid,Lx,Rx,p<<1);
		if(Lx>mid)return Query(mid+1,R,Lx,Rx,p<<1|1);
		return Query(L,mid,Lx,mid,p<<1)+Query(mid+1,R,mid+1,Rx,p<<1|1)-(getfa(mid)==getfa(mid+1));
	}
}ST;
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		ST.Init();
		mp.clear();
		memset(Fr,0,sizeof(Fr));
		int n,m,id=0;
		scanf("%d%d",&n,&m);
		for(int i=1;i<M;i++)fa[i]=i;//初始并查集 
		for(int i=1;i<=n;i++){
			scanf("%d",&C[i]);
			if(mp[C[i]]==0)mp[C[i]]=++id,Fr[mp[C[i]]]=i;
			else fa[i]=Fr[mp[C[i]]];
		}
		for(int i=1;i<=n;i++)ST.Updata(1,n,i,ST.Root[mp[C[i]]],1);
		while(m--){
			int op,x,y;
			scanf("%d%d%d",&op,&x,&y);
			if(op==1){
				if(x==y)continue;
				int a=mp[x],b=mp[y];
				mp[x]=0;
				if(a!=0&&b!=0)fa[Fr[a]]=Fr[b],Fr[a]=0;//两个代表之间的合并,主要要清空被合并的a,注意前面if语句的条件 
				if(b!=0)ST.Union(a,b);
				else mp[y]=a;
			}else {
				printf("%d\n",ST.Query(1,n,x,y,1));
			}
		}
	}
	return 0;
}

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