【Atcoder】ABC183 F Confluence 并查集+启发式合并

题目链接

题意

现在有 n 个学生,第 i 个学生所在班级为 \(c_i\)

现在他们要去上学,某个学生在去上学的路上可能会遇到其他的学生并加入到该集合。

现在有两种操作:

  1. x y,表示第 x 个学生所在集合和第 y 个学生所在集合相遇
  2. x y 表示查询和第 x 个学生在同一个集合中 y 班级的学生。

思路

第一个很明显是并查集,但第二个查询却不好解决。

首先不可能维护每个集合中所有班级的学生数量,数组都开不了这么大。

稍微优化一下,只维护出现在该集合中的班级的学生数量。

那合并的时候我们怎么办呢?

启发式合并

暴力将集合较小的合并到较大的中去。

使用一个二维map来维护每个集合中出现了那些班级以及这些班级的学生数量。

代码

#include <algorithm>
#include <iostream>
#include <map>
#include <stdio.h>
#include <string.h>
#define pb push_back
typedef long long ll;
using namespace std;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;

int fa[N];
map<int, map<int, int>> mp;
int find(int x)
{
    if (fa[x] == x)
        return x;
    return fa[x] = find(fa[x]);
}
int c[N];
int main()
{
    int n, q;
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &c[i]);
        mp[i][c[i]] = 1;
        fa[i] = i;
    }
    while (q--) {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (op == 1) {
            int xx = find(x), yy = find(y);
            if (xx != yy) {
                if (mp[xx].size() > mp[yy].size()) {
                    swap(xx, yy);
                }
                fa[xx] = yy;
                for (map<int, int>::iterator it = mp[xx].begin(); it != mp[xx].end(); ++it) {
                    mp[yy][it->first] += it->second;
                }
            }
        } else {
            int xx = find(x);
            map<int, int>::iterator pos = mp[xx].find(y);
            if (pos != mp[xx].end())
                printf("%d\n", mp[xx][y]);
            else
                printf("0\n");
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/valk3/p/14000746.html