【PAT】10 并查集

第十章 并查集

AcWing 1485. 战争中的城市

问题描述

分析

  • 本题的思路是对于每个去掉的城市,求一遍连通分量的个数,最终需要修建的最少高速公路的数目就是连通分量个数减一的结果。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 1010, M = 500010;

int n, m, k;  // 点数、边数、查询数
int p[N];

struct Edge {
    int a, b;
} e[M];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++) scanf("%d%d", &e[i].a, &e[i].b);
    
    while (k--) {
        int x;
        scanf("%d", &x);
        for (int i = 1; i <= n; i++) p[i] = i;
        
        int cnt = n - 1;
        for (int i = 0; i < m; i++) {
            int a = e[i].a, b = e[i].b;
            if (a != x && b != x) {
                int pa = find(a), pb = find(b);
                if (pa != pb) {
                    p[pa] = pb;
                    cnt--;
                }
            }
        }
        
        printf("%d\n", cnt - 1);
    }
    
    return 0;
}

AcWing 1604. 家产

问题描述

分析

  • 首先读入所有边的信息,然后合并在一个连通块中的点。注意因为题目输出时要求输出整个家族中最小的编号,在合并的时候,根节点值较大的合并到值较小的即可。

代码

  • C++
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
int p[N], c[N], hc[N], ha[N];  // 并查集、家庭人数、房子数,房子面积
bool st[N];  // 记录该编号是否存在

struct Edge {
    int a, b;
} e[N];

struct Family {
    int id, c, hc, ha;
    
    bool operator< (const Family& t) const {
        // ha / c ? t.ha / t.c
        if (ha * t.c != t.ha * c) return ha * t.c > t.ha * c;
        return id < t.id;
    }
};

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    
    cin >> n;
    int m = 0;
    for (int i = 0; i < n; i++) {
        int id, father, mother, k;
        cin >> id >> father >> mother >> k;
        st[id] = true;
        if (father != -1) e[m++] = {id, father};
        if (mother != -1) e[m++] = {id, mother};
        
        for (int i = 0; i < k; i++) {
            int son;
            cin >> son;
            e[m++] = {id, son};
        }
        
        cin >> hc[id] >> ha[id];
    }
    
    for (int i = 0; i < N; i++) p[i] = i, c[i] = 1;
    for (int i = 0; i < m; i++ ) {
        int a = e[i].a, b = e[i].b;
        st[a] = st[b] = true;
        int pa = find(a), pb = find(b);
        if (pa != pb) {
            if (pa < pb) swap(pa, pb);
            c[pb] += c[pa];
            hc[pb] += hc[pa];
            ha[pb] += ha[pa];
            p[pa] = pb;
        }
    }
    
    vector<Family> family;
    for (int i = 0; i < N; i++) 
        if (st[i] && p[i] == i)
            family.push_back({i, c[i], hc[i], ha[i]});
    
    sort(family.begin(), family.end());
    
    cout << family.size() << endl;
    for (auto f : family)
        printf("%04d %d %.3lf %.3lf\n", f.id, f.c, (double) f.hc / f.c, (double) f.ha / f.c);
    
    return 0;
}

AcWing 1608. 森林里的鸟

问题描述

分析

  • 假设鸟的数量为tot,初始每个鸟在一个集合中,使用cnt记录合并的次数,因为每合并一次,集合的数量就减少一个,因此最终集合的数量为tot-cnt,也就是树的数量。

  • 后面查询两个鸟是否在同一个集合中,直接查询即可。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 10010;

int n;
int birds[15];  // 存储每张图片中的鸟的编号
int p[N];
bool st[N];  // 记录出现的鸟

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    
    scanf("%d", &n);
    for (int i = 0; i < N; i++) p[i] = i;
    
    int cnt = 0;  // 记录合并的次数
    for (int i = 0; i < n; i++) {
        int k;
        scanf("%d", &k);
        
        for (int j = 0; j < k; j++) {
            scanf("%d", &birds[j]);
            st[birds[j]] = true;
        }
        
        for (int j = 1; j < k; j++) {
            int a = birds[j - 1], b = birds[j];
            a = find(a), b = find(b);
            if (a != b) {
                p[a] = b;
                cnt++;
            }
        }
    }
    
    int tot = 0;
    for (int i = 1; i < N; i++) tot += st[i];
    
    printf("%d %d\n", tot - cnt, tot);
    
    int q;
    scanf("%d", &q);
    while (q--) {
        int a, b;
        scanf("%d%d", &a, &b);
        if (find(a) == find(b)) puts("Yes");
        else puts("No");
    }
    
    return 0;
}

AcWing 1597. 社会集群

问题描述

分析

  • 我们使用对于每个爱好建立一个vector,这样所有有这个爱好的人会在同一个vector中,之后合并每个vector中的人即可。

  • 使用cnt数组记录每个集合中元素的数量,最后排序输出即可。

代码

  • C++
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
vector<int> hobby[N];
int p[N];
int cnt[N];  // 每个集合中元素个数

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int k;
        scanf("%d:", &k);
        while (k--) {
            int h;
            scanf("%d", &h);
            hobby[h].push_back(i);
        }
    }
    
    for (int i = 1; i <= n; i++) p[i] = i;
    
    for (int i = 1; i <= 1000; i++)  // 遍历1000个爱好
        for (int j = 1; j < hobby[i].size(); j++) {
            int a = hobby[i][0], b = hobby[i][j];  // (a, b)有相同的爱好i
            p[find(a)] = find(b);
        }
    
    for (int i = 1; i <= n; i++) cnt[find(i)]++;
    
    sort(cnt, cnt + N, greater<int>());
    
    int k = 0;
    while (cnt[k]) k++;
    
    printf("%d\n", k);
    printf("%d", cnt[0]);
    for (int i = 1; i < k; i++)
        printf(" %d", cnt[i]);
    puts("");
    
    return 0;
}

AcWing 836. 合并集合

问题描述

分析

  • 并查集模板题。关于并查集的讲解可以参考:并查集

代码

  • C++
#include <iostream>

using namespace std;

const int N = 100010;

int p[N];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);  // 路径压缩
    return p[x];
}

int main() {
    
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) p[i] = i;  // 并查集初始化
    
    while (m--) {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if (*op == 'M') p[find(a)] = find(b);
        else {
            if (find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值