Luogu3201 梦幻布丁 - 启发式合并 -

题目链接:https://www.luogu.com.cn/problem/P3201

题解:
考虑启发式合并,即每次把数量小的颜色块合并到大的颜色块上
对于每个颜色块,我们可以使用链表维护,具体写法可以采用前向星。(当前位置的nxt置为head[x], head[x]=当前位置),这里head[p]=i,p表示颜色,i表示位置
这样会产生一个问题:如果题目要求的是把数量大\(x\)颜色的合并到小的\(y\)颜色,这样颜色就是反的?
我们可以维护一个颜色数组\(f[i]\)表示\(i\)颜色块当前的真实颜色,如果出现上述情况,就swap一下二者的真实颜色,并把y全部标记到x上,这样查询真实颜色的时候查到的就是\(f[x]\)就是互换前的\(f[y]\)
改变颜色时就暴力修改,由于每次小的颜色块大小至少*2,因此最多修改log次,时间复杂度\(O(nlogn)\)

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
const int maxn=1e6+5;

int n,m;
int st[maxn], a[maxn], nxt[maxn],head[maxn],sz[maxn],f[maxn];
int ans = 1;

void merge(int x,int y){  // 把 x 颜色块均修改为 y 颜色块 (x y 不代表真实颜色)
	for(int i=head[x];i;i=nxt[i]){
		if(a[i+1] == y)-- ans;
		if(a[i-1] == y)-- ans;
	}
	for(int i=head[x];i;i=nxt[i])a[i] = y;
	nxt[st[x]] = head[y];head[y] = head[x];sz[y] += sz[x];
	st[x] = 0; sz[x] = 0; head[x] = 0;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=2;i<=n;i++)if(a[i] != a[i-1])++ ans;
	for(int i=1;i<=n;i++){
		if(!head[a[i]])st[a[i]] = i;
		nxt[i] = head[a[i]]; head[a[i]] = i;
		f[a[i]] = a[i];
		++ sz[a[i]];
	}
	
	while(m --){
		int op;scanf("%d",&op);
		if(op == 2)printf("%d\n",ans);
		else{
			int x,y;scanf("%d%d",&x,&y);
			if(sz[f[x]] > sz[f[y]])swap(f[x], f[y]);
			if(x == y || !sz[f[x]])continue;
			merge(f[x], f[y]);
		}
	}
	
	return 0;
}
posted @ 2022-09-05 14:58  SkyRainWind  阅读(12)  评论(0编辑  收藏  举报