洛谷 P3201 [HNOI2009] 梦幻布丁 题解
一、题目:
二、思路:
这是一道比较清奇的启发式合并题目。
考虑对于每种颜色建立一个集合。集合\(c\)中维护的是颜色\(c\)的所有位置(即下标)。
记\(ans\)为起初有多少段颜色。
那么每当要将所有颜色为\(x\)的布丁染成\(y\)颜色的时候(不过好像布丁不能染色),我们取枚举集合\(x\)中的所有位置\(i\),若\(c[i-1]=y\),将\(ans--\);若\(c[i+1]=y\),将\(ans--\)。然后将\(c[i]\)全部变成\(y\),把集合\(x\)合并到集合\(y\)即可。
然后呢?然后复杂度就爆炸了。
乍一看这题好像不能启发式合并,因为题目中规定要将\(x\)集合合并到\(y\)集合中去,我们不能根据集合大小来决定将谁合并到谁上。
再仔细一想,难道题目说啥我就干啥?为了保证复杂度,我非得把\(siz\)小的集合合并到\(siz\)大的集合上去,本质上好像并没有什么影响,只是颜色的名字换了一下而已。所以当题目的要求和我的需要冲突的时候,我只需要额外维护一下题目中说的颜色对应着我程序中的哪种颜色即可。
说白了,也就是建立一个从“题目的编色方式”到“我程序中的编色方式”的映射。
然后呢?然后就做完了。
三、代码:
//我们用链表这种数据结构来维护集合
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 1e6 + 5;
int n, m, head[maxn], tot, siz[maxn], ans;
int a[maxn];
int reality[maxn];
struct Node {
int pos, nxt;
}e[maxn];
inline void add(int c, int i) {
e[++ tot].pos = i; e[tot].nxt = head[c];
head[c] = tot;
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; ++ i) {
a[i] = read();
add(a[i], i);
++ siz[a[i]];
reality[a[i]] = a[i];
if (a[i] != a[i - 1]) ++ ans;
}
while (m --) {
int opt = read();
if (opt == 2) printf("%d\n", ans);
else {
int x = read(), y = read();
if (x == y) continue;
if (siz[reality[x]] > siz[reality[y]]) swap(reality[x], reality[y]);
x = reality[x]; y = reality[y];
if (!siz[x]) continue;
for (int i = head[x]; i; i = e[i].nxt) {
int p = e[i].pos;
if (a[p + 1] == y) -- ans;
if (a[p - 1] == y) -- ans;
}
int las = 0;
for (int i = head[x]; i; i = e[i].nxt) {
int p = e[i].pos;
a[p] = y;
las = i;
}
e[las].nxt = head[y], head[y] = head[x];
siz[y] += siz[x];
head[x] = 0, siz[x] = 0;
}
}
return 0;
}