[HNOI2009] 梦幻布丁
题目描述
$n$ 个布丁摆成一行,进行 $m$ 次操作。每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色。
例如,颜色分别为 $1,2,2,1$ 的四个布丁一共有 $3$ 段颜色.
输入格式
第一行是两个整数,分别表示布丁个数 $n$ 和操作次数 $m$。
第二行有 $n$ 个整数,第 $i$ 个整数表示第 $i$ 个布丁的颜色 $a_i$。
接下来 $m$ 行,每行描述一次操作。每行首先有一个整数 $op$ 表示操作类型:
- 若 $op = 1$,则后有两个整数 $x, y$,表示将颜色 $x$ 的布丁全部变成颜色 $y$。
- 若 $op = 2$,则表示一次询问。
输出格式
对于每次询问,输出一行一个整数表示答案。
样例 #1
样例输入 #1
4 3
1 2 2 1
2
1 2 1
2
样例输出 #1
3
1
提示
样例 1 解释
初始时布丁颜色依次为 $1, 2, 2, 1$,三段颜色分别为 $[1, 1], [2, 3], [4, 4]$。
一次操作后,布丁的颜色变为 $1, 1, 1, 1$,只有 $[1, 4]$ 一段颜色。
数据规模与约定
对于全部的测试点,保证 $1 \leq n, m \leq 10^5$,$1 \leq a_i ,x, y \leq 10^6$。
提示
请注意,不保证颜色的编号不大于 $n$,也不保证 $x \neq y$,$m$ 不是颜色的编号上限。
解题思路
容易想到的暴力做法是,用 std::vector
维护各个颜色的下标有那些,对于询问的 $x$ 和 $y$,把颜色是 $x$ 的下标 $i$ 都改成 $a_i = y$,并将这些下标都存到颜色是 $y$ 的 std::vector
中,同时清空颜色是 $x$ 的 std::vector
,最后枚举数组 $a$ 统计颜色段即可。
可以发现随着询问的增加,答案会逐渐变小,这是因为每次将颜色 $x$ 修改成 $y$,颜色 $y$ 的下标会增多,颜色 $x$ 的下标清零,意味着颜色段只可能变少,不会变多。为此我们只关心当颜色是 $x$ 的下标 $i$ 变成颜色 $y$ 时,是否会减少颜色段,即如果 $a_{i - 1} = y$,$a_{i+1} = y$ 时,颜色段就会减少。
这时就可以用启发式合并了,启发式合并的概念就是如果要合并两个集合的元素,那么应该把元素少的集合合并到元素多的集合,这样可以保证合并次数为 $O(n \log{n})$ 的级别。考虑每个元素对合并次数的贡献是多少,由于该元素只会合并到元素数量比它大的集合中,因此合并后该元素所在集合的大小至少为原来集合的两倍,假设一共有 $n$ 个元素,那么这个元素最多能合并 $O(\log{n})$ 次,因此所有元素的合并次数即总共的合并次数就是 $O(n \log{n})$ 级别。
在这题中我们应该把颜色 $x$ 和 $y$ 中下标数量少的合并到另外一个中,如果颜色 $x$ 的 std::vector
小于颜色 $y$ 的 std::vector
直接合并即可,同时把颜色是 $x$ 的 $a_i$ 修改成 $y$。否则我们应该把颜色 $y$ 的下标合并到颜色 $x$ 中,这样做的话要把颜色是 $y$ 的 $a_i$ 修改成 $x$,这样做是没问题的,并不会影响颜色段的数量,但问题是如果下次询问的颜色是 $y$ 就会出错,因为此时 $a$ 中没有颜色 $y$,都用 $x$ 来表示了。为此我们可以维护一个数组 $c$,$c_x$ 表示颜色 $x$ 实际上用 $c_x$ 来表示。所以如果遇到颜色 $x$ 的 std::vector
大于颜色 $y$ 的 std::vector
的情况,我们应该让颜色 $x$ 用下标少的颜色 $c_y$ 来表示,为此只需交换 $c_x$ 和 $c_y$,然后把颜色是 $c_x$ 的下标合并到颜色 $c_y$ 中即可,同时把颜色是 $c_x$ 的 $a_i$ 修改成 $c_y$。
AC 代码 时间复杂度为 $O(n \log{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = 1e6 + 10;
int a[N];
vector<int> p[M];
int c[M];
int main() {
int n, m;
scanf("%d %d", &n, &m);
int ret = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
c[a[i]] = a[i];
p[a[i]].push_back(i);
if (a[i] != a[i - 1]) ret++;
}
while (m--) {
int op;
scanf("%d", &op);
if (op == 1) {
int x, y;
scanf("%d %d", &x, &y);
if (x == y) continue;
if (p[c[x]].size() > p[c[y]].size()) swap(c[x], c[y]);
for (auto &i : p[c[x]]) {
if (a[i - 1] == c[y]) ret--;
if (a[i + 1] == c[y]) ret--;
}
for (auto &i : p[c[x]]) {
a[i] = c[y];
p[c[y]].push_back(i);
}
p[c[x]].clear();
}
else {
printf("%d\n", ret);
}
}
return 0;
}
参考资料
数据结构学习笔记(8) 启发式合并:https://zhuanlan.zhihu.com/p/560661911
题解 P3201 【[HNOI2009]梦幻布丁】:https://siyuan.blog.luogu.org/solution-p3201
标签:std,下标,颜色,布丁,梦幻,合并,vector,HNOI2009 From: https://www.cnblogs.com/onlyblues/p/17891269.html