[HNOI2009] 梦幻布丁

link

思路不难想,不就是一个启发式合并吗。但代码细节比较多。

首先是数据结构的选择。读完题我们发现,我们需要的是一种支持合并操作的数据结构(但似乎大多数数据结构都支持合并)。然后我就想错了。在选择数据结构之前需要理清思路,我们的想法应该是枚举小集合的所有元素,处理,放入大集合中。所以可以发现,集合中的元素其实不一定要有序,所以甚至列表都可以解决它(zz的我一口气写了个Splay然后死掉了)。

然后是答案的更新。思路应该是这样的:假如修改之后的颜色是一种全新的颜色,那么显然颜色段数不会发生改变;由此可以推出,只有当原颜色和现颜色接壤时才会造成答案的变化。判断的话可以直接在原序列上进行,保证原数列颜色正确则需要另一次遍历小集合(因为过早修改会错把自己改过去的位置当成原来的现颜色)。

最后是对颜色指针的处理。启发式合并的写法时当集合大小关系不对时进行swap,这就带来了一个不小的问题。假如已然把所有的1改成2,但是1的集合比较大,那么下次询问把1改成3时就会出现一开始1和2的位置都会变成3的状况,显然是错误的。于是引入颜色指针的概念。用\(f_i\)来指代现在颜色i对应的集合是哪个,很明显当交换x和y时,判断依据应该是两个f对应集合的大小关系,交换时也应该连同f一起交换。

其它没什么了。代码:

#include<cstdio>
//#define zczc
const int N=1000010;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline void swap(int &s1,int &s2){
	int s3=s1;s1=s2;s2=s3;return;
}

int cnt,s[N],next[N],head[N],size[N],real[N];

int m,n,ans;

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);
	int op,in,x,y,last;
	for(int i=1;i<N;i++)real[i]=i;
	for(int i=1;i<=m;i++){
		read(s[i]);next[i]=head[s[i]];head[s[i]]=i;size[s[i]]++;
		if(s[i]^s[i-1])ans++;
	}
	while(n--){
		read(op);
		if(op==2){printf("%d\n",ans);continue;}
		read(x);read(y);
		if(size[real[x]]>size[real[y]]){swap(real[x],real[y]);}
		x=real[x],y=real[y];
		if(x==y||size[x]==0)continue;
		for(int i=head[x];i;i=next[i]){
			ans-=(s[i-1]==y)+(s[i+1]==y);
			if(!next[i]){next[i]=head[y];head[y]=head[x];break;}
		}
		for(int i=head[x];i;i=next[i]){
			if(s[i]==y)break;s[i]=y;
		}
		head[x]=0;size[y]+=size[x];size[x]=0;
	}
	
	return 0;
}
posted @ 2022-06-18 21:21  Feyn618  阅读(24)  评论(0编辑  收藏  举报