[ABC329F] Colored Ball 题解
暴力算法
首先一看题目,相信大家一眼就看出了并查集。但是做着做着,你就会发现,这根本就不是一个并查集啊!原因如下:
- 这里的合并是单向的,即 \(a\) 合并到 \(b\) 不等于 \(b\) 合并到 \(a\);
- 输出的是不同颜色的球的数量,但是使用并查集实现非常麻烦。
所以我们可以想出一种十分暴力的解法:开上 \(n\) 个 set,然后使用 merge() 合并函数进行模拟。但是有一种特殊情况,就是将 \(N-1\) 个球的盒子合并到只有 \(1\) 个球的盒子里面,那么就需要 \(N-1\) 次遍历。这种算法的最高时间复杂度是 \(\mathcal O(NQ)\) 的,而 \(N,Q\le 2\times 10^5\),因此会超时。有没有一种方法,可以减低合并的时间呢?
启发式合并
什么是启发式合并?就是将小的盒子合并到大的盒子里面,这样子每次合并时间复杂度就取决于小的盒子的大小。这种合并方式的平均时间复杂度是 \(\mathcal O(\log_2 N)\) 的,非常稳定。
但是我们发现,如果把 \(a\) 移动到 \(b\) 改为了 \(b\) 移动到 \(a\),那么最后两个 set 还需要交换,因为方向改变了。而 set 的赋值速度非常慢,最坏一次交换还是会卡到 \(\mathcal O(N)\)。所以,我们不能直接移动 set,而是交换指向 set 的指针。
所以我们就可以使用 \(N\) 个指向 set 的指针,按照题意模拟,但是交换的时候要交换两个指针本身,而不是指针指向的 set。于是,本题就可以使用指针巧妙地解决了!
注:-> 运算符表示访问指针所指对象的成员。
代码
#include <iostream>
#include <unordered_set> // 使用无序集合
using namespace std;
const int kMaxN = 2e5 + 5;
int n, q;
unordered_set<int> *s[kMaxN]; // 指向 set 的指针,注意指针的操作
int main() {
cin >> n >> q;
for (int i = 1; i <= n; i++) {
s[i] = new unordered_set<int>; // 为每一个 set 申请空间
}
for (int i = 1, x; i <= n; i++) {
cin >> x; // 输入每个球
s[i]->insert(x); // 塞入初始的一个球
}
for (int i = 1, a, b; i <= q; i++) {
cin >> a >> b;
if (s[a]->size() > s[b]->size()) { // 如果 A 的大小大于 B
swap(s[a], s[b]); // 交换所指的位置,不是交换 set!
}
s[b]->merge(*s[a]), s[a]->clear(); // 将 A 移动到 B,并把 A 给清空
cout << s[b]->size() << '\n'; // set 自动去重,size 就是不同的球的数量
}
return 0;
}
由于使用了 unordered_set,因此时间复杂度为 \(\mathcal O(Q\log_2 N)\),实测 \(819\) 毫秒。

浙公网安备 33010602011771号