【ybt金牌导航6-2-1】【luogu P3201】梦幻布丁 / 启发式合并例题

梦幻布丁

题目链接:ybt金牌导航6-2-1 / luogu P3201

题目大意

有一些颜色块,然后它有时会把所有颜色改成另一种颜色,有时会询问你现在有多少个颜色段。一个颜色段就是一段相同颜色的连续颜色块。

思路

那这个我们可以发现,其实每次更改就是把一些部分合并。
合并了之后,就不会再分开。

那我们可以用启发式合并,合并的时候,枚举个数少的,把它合并到大的当中,以减少时间。

那它是如何合并的呢?
假设我们已经可以找到某种颜色的所有块,然后来看。
如果它的左边是要更改成的颜色,那颜色段就减一。如果它的右边是更改成的颜色,那么颜色段就减一。(两个同时发生就减二)

那一开始我们可以求出一开始的颜色段数。

然后现在就是看如何找到某种颜色的所有块,然后这个其实可以用链表来维护。

然后就可以了。

代码

#include<cstdio>
#include<algorithm>

using namespace std;

int n, m, a[100001], b[1000001], s[1000001];
int le[1000001], ans, op, x, y, nxt[1000001];
int son[1000001];

int main() {
    
    
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
    
    
		scanf("%d", &a[i]);
		b[a[i]] = a[i];//记录它所在链的颜色
		s[a[i]]++;//记录这个颜色的个数
		nxt[i] = le[a[i]];//链表连向它之前的头
		if (!le[a[i]]) {
    
    
			son[a[i]] = i;//记录这个颜色的链的末端
		}
		le[a[i]] = i;//链表头设为它
		if (a[i] != a[i - 1]) ans++;//求出初始颜色段
	}
	
	for (int i = 1; i <= m; i++) {
    
    
		scanf("%d", &op);
		if (op == 1) {
    
    
			scanf("%d %d", &x, &y);
			
			if (x == y) continue;//同一个颜色相互转换等于没有转
			
			if (s[b[x]] > s[b[y]]) swap(b[x], b[y]);
			//启发式合并,个数小的合并到个数大的
			
			if (!s[b[x]]) continue;//最小的那个没有,就相当于没有合并
			
			x = b[x];//找到它们现在的颜色
			y = b[y];
			for (int i = le[x]; i; i = nxt[i]) {
    
    
				if (a[i + 1] == y) ans--;//左边变成了一样的
				if (a[i - 1] == y) ans--;//右边变成了一样的			}
			
			for (int i = le[x]; i; i = nxt[i]) {
    
    
				a[i] = y;//改颜色
			}
			
			nxt[son[y]] = le[x];
			//把两个链连在一起(前面的链尾的下一个是后面的头)
			son[y] = son[x];//链尾变成后面的链尾
			
			le[x] = 0;//清空
			son[x] = 0;
			s[y] += s[x];//颜色个数增加
			s[x] = 0;
		}
		else {
    
    
			printf("%d\n", ans);
		}
	}
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43346722/article/details/114006643