[HNOI2009] 梦幻布丁
思路不难想,不就是一个启发式合并吗。但代码细节比较多。
首先是数据结构的选择。读完题我们发现,我们需要的是一种支持合并操作的数据结构(但似乎大多数数据结构都支持合并)。然后我就想错了。在选择数据结构之前需要理清思路,我们的想法应该是枚举小集合的所有元素,处理,放入大集合中。所以可以发现,集合中的元素其实不一定要有序,所以甚至列表都可以解决它(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;
}
一如既往,万事胜意