重返天梯-并查集问题

天梯并查集

这是天梯中很喜欢出的一类题。虽然好久没出了hhhhh

L2-007 家庭房产 (25 分) (往结点编号小的合并)

题目分析

给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。
原题链接


能想到是并查集并不困难,但处理数据的输入输出还是有一定的难度

C++

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e4 + 10;

struct Data {
    int id, fid, mid, num, area;
    int cid[10];
}dat[N];

struct Ans {
    int id, people;
    double num, area;
    bool flag; // 设置标记保证不出现除以0的情况
    bool operator< (const Ans& W) const {
        if (area == W.area) return id < W.id;
        return area > W.area;
    }
}ans[N];

int n;
int p[N];
bool st[N];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
// 保留家庭成员的最小编号
void merge(int a, int b) {
    a = find(a), b = find(b);
    if (a < b)
        p[b] = a;
    else if (a > b)
        p[a] = b;
}


int main() {
    cin >> n;
    
    for (int i = 1; i < N; i++) p[i] = i;
    
    for (int i = 0; i < n; i++) {
        int id, fid, mid, k;
        cin >> id >> fid >> mid >> k;
        st[id] = true;
        dat[i].id = id, dat[i].fid = fid, dat[i].mid = mid;
        
        if (fid != -1) {
            st[fid] = true;
            merge(id, fid);
        }
        
        if (mid != -1) {
            st[mid] = true;
            merge(id, mid);
        }
        
        for (int j = 0; j < k; j++) {
            int x;
            cin >> x;
            dat[i].cid[j] = x;
            st[x] = true;
            merge(id, x);
        }
        // 读入房产数及总面积
        cin >> dat[i].num >> dat[i].area;
    }
    // 遍历读入的数据,累计房产和面积
    for (int i = 0; i < n; i++) {
        int id = find(dat[i].id);
        ans[id].id = id;
        ans[id].flag = true;
        ans[id].num += dat[i].num;
        ans[id].area += dat[i].area;
    }
    // 统计人数
    for (int i = 0; i < N; i++) {
        if (st[i]) {
            int family = find(i);
            ans[family].people++;
        }
    }
    // 计算人均并统计有多少个家庭
    int cnt = 0;
    for (int i = 0; i < N; i++) {
        if (ans[i].flag) {
            cnt++;
            ans[i].num /= ans[i].people;
            ans[i].area /= ans[i].people;
        }
    }
    
    sort(ans, ans + N);
    
    cout << cnt << endl;
    for (int i = 0; i < N; i++) {
        if (ans[i].flag) {
            printf("%04d %d %.3f %.3f\n", ans[i].id, ans[i].people, ans[i].num, ans[i].area);
        }
    }
    
    return 0;
}

L2-010 排座位 (25 分)

题目描述

这里假设朋友的朋友也是朋友。但敌人的敌人并不一定就是朋友,朋友的敌人也不一定是敌人。只有单纯直接的敌对关系才是绝对不能同席的。
原题链接


这道题要分析的是要把哪一种关系用并查集进行维护。
上述加粗部分提示到,朋友的朋友也是朋友,说明我们可以维护一个朋友的集合,来便于查询。
而敌对关系我们用一个数组来记录即可

C++

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int p[N], ene[N][N];
int n, m, k;

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 = 1; i <= n; i++) p[i] = i;
    
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;
        
        if (c == 1) {
            a = find(a), b = find(b);
            p[a] = b;
        } else if (c == -1) {
            ene[a][b] = -1, ene[b][a] = -1;
        }
    }
    
    while (k--) {
        int a, b;
        cin >> a >> b;
        bool isFri = find(a) == find(b) ? true:false;
        if (ene[a][b] == -1 && isFri) puts("OK but...");
        else if (!isFri && ene[a][b] != -1) puts("OK");
        else if (isFri && ene[a][b] != -1) puts("No problem");
        else puts("No way");
    }
    
    return 0;
}

L2-024 部落 (25 分)

在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里,于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同一个部落。
原题链接


圈子内部直接调用并查集,如果圈子与圈子之间有公共编号,则会使两个圈子合并

C++

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int n, q;
int p[N];
bool st[N];

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

int main() {
    for (int i = 1; i <= N; i++) p[i] = i;
    cin >> n;
    
    set<int> S;
    for (int i = 0; i < n; i++) {
        int k, x;
        cin >> k >> x;
        st[x] = true;
        S.insert(x);
        for (int j = 0; j < k - 1; j++) {
            int y;
            cin >> y;
            S.insert(y);
            st[y] = true;
            p[find(y)] = find(x);
        }
    }
    
    int cnt = 0;
    for (int i = 1; i <= N; i++) {
        if (p[i] == i && st[i]) cnt++;
    }
    
    cout << S.size() << ' ' << cnt << endl;
    
    cin >> q;
    while (q--) {
        int a, b;
        cin >> a >> b;
        if (find(a) == find(b)) puts("Y");
        else puts("N");
    }
    
    return 0;
}

L3-003 社交集群 (30 分)

题目描述

给出每个人的兴趣编号,拥有共同兴趣的人在同一个圈子。
问有多少个圈子,每个圈子里有多少人。

原题链接

一开始在犹豫到底对人进行合并,还是对兴趣点进行合并。没有第一时间思考出来

对兴趣点进行合并,每个人保留第一个兴趣。在所有兴趣合并完成后,遍历每一个的兴趣,从而确定其对应的圈子,并统一圈子个数,及圈子内部人员个数

C++

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int p[N];
int n;
vector<int> v;

struct Node {
    bool flag;
    int peo;
    
    bool operator< (const Node& w) const {
        return peo > w.peo;
    }
}ans[N];

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


int main() {
    for (int i = 1; i < N; i++) p[i] = i;
    
    scanf("%d", &n);
    
    for (int i = 0; i < n; i++) {
        int k, h;
        
        scanf("%d: %d", &k, &h);
        v.push_back(h);
        if (k == 1) continue;
        
        for (int j = 0; j < k - 1; j++) {
            int h2;
            cin >> h2;
            p[find(h2)] = find(h);
        }
    }
    
    for (auto t: v) {
        ans[find(t)].flag = true;
        ans[find(t)].peo++;
    }
    int cnt = 0;
    for (int i = 1; i < N; i++) {
        if (ans[i].flag) cnt++;
    }
    
    cout << cnt << endl;
    sort(ans, ans + N);
    
    for (int i = 0; i < cnt; i++) {
        if (!i) cout << ans[i].peo;
        else cout << ' ' << ans[i].peo;
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeSlogan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值