[ABC329F] Colored Ball 题解

暴力算法

首先一看题目,相信大家一眼就看出了并查集。但是做着做着,你就会发现,这根本就不是一个并查集啊!原因如下:

  1. 这里的合并是单向的,即 \(a\) 合并到 \(b\) 不等于 \(b\) 合并到 \(a\)
  2. 输出的是不同颜色的球的数量,但是使用并查集实现非常麻烦。

所以我们可以想出一种十分暴力的解法:开上 \(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\) 毫秒。

posted @ 2023-11-19 09:44  haokee  阅读(58)  评论(0)    收藏  举报