梦幻布丁
题目链接: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
", ans);
}
}
return 0;
}