并查集专题训练

本文探讨了并查集在解决组合优化问题中的应用,如01背包问题、无线网络维护、嫌疑人分析、超市商品选择等。通过维护集合大小、距离信息和权值,实现高效的数据结构操作,解决了一系列复杂问题。并查集的核心在于快速查找元素所属的集合,并在需要时合并集合,同时更新相关属性。
摘要由CSDN通过智能技术生成

搭配购买
并查集+01背包;
先合并分块,然后按照01背包模型求解

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 10010;
int p[N], c[N], d[N];
int v[N], w[N];
int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}
int f[N];
void solve() {
    int n, m, W;
    cin >> n >> m >> W;
    for (int i = 1; i <= n; i++) cin >> c[i] >> d[i], p[i] = i;
    while (m--) {
        int u, v;
        cin >> u >> v;
        if (find(u) == find(v)) continue;
        else {
            c[find(u)] += c[find(v)];
            d[find(u)] += d[find(v)];
            p[find(v)] = find(u);
        }
    }
    int cnt = 1;
    for (int i = 1; i <= n; i++) {
        if (find(i) == i) v[cnt] = c[find(i)], w[cnt] = d[find(i)], cnt++;


    }
    for (int i = 1; i < cnt; i++) {
        for (int j = W; j >= v[i]; j--) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[W] << endl;




}
int main() {
    solve();

    return 0;
}

//维护集合大小使用size数组存储大小即可;
维护每个点到根节点的距离:

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

Wireless Network
裸并查集+枚举

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1010;
struct node {
    int x, y;
} pos[N];
int vis[N];//只有已经被维修的电脑才能连在一起;
int p[N];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
void solve() {
    int n, d;
    scanf("%d%d", &n, &d);
    for (int i = 1; i <= n; i++) p[i] = i, scanf("%d%d", &pos[i].x, &pos[i].y);
    char op[2];
    int m;
    int a, b;
    while (~scanf("%s", op)) {
        if (op[0] == 'O') {
            scanf("%d", &m);
            vis[m] = 1;//被维修;
            for (int i = 1; i <= n; i++) {
                if ((pos[i].x - pos[m].x) * (pos[i].x - pos[m].x) + (pos[i].y - pos[m].y) * (pos[i].y - pos[m].y) <= d * d and vis[i]) {//判断距离
                    int pi = find(i), pm = find(m);
                    p[pi] = pm;
                }
            }
        } else {
            scanf("%d%d", &a, &b);
            if (find(a) == find(b)) puts("SUCCESS");
            else puts("FAIL");
        }
    }
}
int main() {

    solve();
    return 0;
}

The Suspects
维护size的并查集

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;
const int N = 30010;
int p[N], s[N];
int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}
void solve() {
    int n, m;
    while (scanf("%d%d", &n, &m), n) {
        for (int i = 0; i < n; i++) p[i] = i, s[i] = 1;

        while (m--) {
            int k, x, lst;
            scanf("%d", &k);
            int cnt = k;

            while (cnt--) {
                scanf("%d", &x);
                if (cnt == k - 1) {
                    lst = x;

                    continue;
                }
                int px = find(x), pl = find(lst);
                if (px != pl) {

                    s[pl] += s[px];
                    p[px] = pl;
                }
                lst = x;
            }
        }
        printf("%d\n", s[find(0)]);
    }
}
int main() {
    solve();

    return 0;
}

How Many Tables
裸并查集统计个数;

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 1010;
int p[N];
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) p[i] = i;
    while (m--) {
        int a, b;
        cin >> a >> b;
        int pa = find(a), pb = find(b);
        p[pa] = pb;
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (find(i) == i) cnt ++;
    }
    cout << cnt << endl;
}
int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

How Many Answers Are Wrong
带权并查集,维护到根节点的距离;
假如两个集合不在一起时,需要合并;
如果两个集合属于同一个根,则需要判断;
合并集合的时候只改变根节点的距离

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 200010;
int p[N], d[N];
int find(int x) {
    if (x != p[x]) {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}
void solve() {
    int n, m;
    while (~scanf("%d%d", &n, &m)) {
        int a, b, s;
        int cnt = 0;
        for (int i = 1; i <= n; i++) p[i] = i, d[i] = 0;
        while (m--) {
            scanf("%d%d%d", &a, &b, &s);
            a--;
            int pa = find(a), pb = find(b);
            if (pa == pb) {
                if (d[b] - d[a] != s) cnt++;
            } else {
                p[pb] = pa;
                d[pb] = d[a] + s - d[b];//假如pb=C,把三角形abc拉成变成一条直线,就能得出这个结论;
            }
        }
        printf("%d\n", cnt);
    }
}
int main() {
    solve();

    return 0;
}

食物链
带权并查集

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 50010;
int p[N], d[N];
int find(int x) {
    if (x != p[x]) {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}
void solve() {

    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) p[i] = i;
    int res = 0;
    while (k--) {
        int s, a, b;
        scanf("%d%d%d", &s, &a, &b);
        if (a > n or b > n) res++;
        else {
            int pa = find(a), pb = find(b);
            if (s == 1) {
                if (pa == pb and (d[a] - d[b]) % 3) res++;
                else if (pa != pb) {
                    p[pa] = pb;
                    d[pa] = d[b] - d[a];
                }
            } else {
                if (a == b) res++;
                else if (pa == pb and (d[a] - d[b] - 1) % 3) res++;
                else if (pa != pb) {
                    p[pa] = pb;
                    d[pa] = d[b] + 1 - d[a];
                }
            }
        }
    }
    printf("%d\n", res);
}
int main() {

    solve();

    return 0;
}

并查集刷题心得:带权并查集,当集合合并时,改变的是未合并时根节点到祖宗节点的距离;这个可以通过三者之间的关系确定;
小希的迷宫
思路,假如两个点已经在同一个集合,那么肯定会有环出现;
最后判断可以判断集合是否只有一个,或者点数减边数等于1;

//点数减边数为1;
//维护size集合;
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
int p[100005], success;
int s[100005];
int edge;
int lst;
void init() {

    for (int i = 0; i < 100005; i++) {
        p[i] = i;
        s[i]=1;
    }
}
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}
void merge(int x, int y) {
    int px = find(x);
    int py = find(y);
    if (px != py) {
        s[py]+=s[px];
        p[px] = py;
    } else
        success = 0;
}
bool check(){
    return s[find(lst)]-edge==1;
}
int main() {
    int n, m, a, b;
    while (scanf("%d %d", &n, &m) != EOF) {
        if (m == 0 and n == 0) {
            printf("Yes\n");
            continue;
        }
        init();

        if (m == -1 and n == -1) {
            break;
        }
        merge(m, n);
        edge=1;
        success = 1;
        // vis[n] =  vis[m] = 1;
        while (scanf("%d %d", &a, &b) , a and b) {
            merge(a, b);
            lst=a;
            // vis[a] = 1, vis[b] = 1;
            range++;
        }
        if (success == 0) {
            printf("No\n");
            
        } else {
            if (check()) {
                printf("Yes\n");
            } else {
                printf("No\n");
            }
        }
    }
    return 0;
}
//判断集合是否只有一个
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
int p[100005], success;
int vis[100005];
void init() {

    for (int i = 0; i < 100005; i++) {
        p[i] = i;
        vis[i] = 0;
    }
}
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}
void merge(int x, int y) {
    int px = find(x);
    int py = find(y);
    if (px != py) {
        p[px] = py;
    } else
        success = 0;
}
bool check(){
    int sum=0;
    for(int i=1;i<=100005;i++){
        if(vis[i] and p[i]==i) sum++;
    }
    return sum==1;
}
int main() {
    int n, m, a, b;
    while (scanf("%d %d", &n, &m) != EOF) {
        if (m == 0 and n == 0) {
            printf("Yes\n");
            continue;
        }
        init();
        if (m == -1 and n == -1) {
            break;
        }
        merge(m, n);
        success = 1;
        vis[n] =  vis[m] = 1;
        while (scanf("%d %d", &a, &b) , a and b) {
            merge(a, b);
            vis[a] = 1, vis[b] = 1;
        }
        if (success == 0) {
            printf("No\n");
            
        } else {
            if (check()) {
                printf("Yes\n");
            } else {
                printf("No\n");
            }
        }
    }
    return 0;
}

Supermarket

//这里并查集的作用是用于记录时间点;
//贪心思路:按照利润大小排序;然后选择最大的利润再让p[px]=px-1;
//说明假如还有同一个时间点的就排到前一个位置;
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 10010;
int p[N];
struct node {
    int x, d;
} a[N];
bool cmp(node a, node b) {
    return a.x > b.x;
}
int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}
void inti() {
    for (int i = 1; i <= N; i++) p[i] = i;
}
void solve() {
    int n;
    while (~scanf("%d", &n)) {
        inti();
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i].x, &a[i].d);
        }
        sort(a + 1, a + 1 + n, cmp);
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            int pa = find(a[i].d);
            if (pa > 0) {
                ans += a[i].x;
                p[pa] = pa - 1;
            }
        }
        printf("%d\n", ans);
    }
}
int main() {

    solve();
    return 0;
}

A Bug’s Life
带权分类并查集

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
int p[2010], d[2010];
int Case = 1;
int find(int x) {
    if (x != p[x]) {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}

void join(int a, int b) {
    int pa = find(a), pb = find(b);
    p[pa] = pb;
    d[pa] = d[b] - d[a] + 1;
}
void solve() {
    int n, m;
    int check = 1;
    scanf("%d%d", &n, &m);
    int a, b;
    for (int i = 1; i <= n; i++) p[i] = i,d[i]=0;
    while (m--) {
        scanf("%d%d", &a, &b);
        int pa = find(a), pb = find(b);
        if (pa != pb) join(a, b);
        else if (pa == pb) {
            if (abs(d[a] % 2 - d[b] % 2) == 0) check = 0;
        }
    }

    printf("Scenario #%d:\n", Case++);
    if (check) printf("No suspicious bugs found!\n");
    else printf("Suspicious bugs found!\n");
    putchar('\n');
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }

    return 0;
}

is a tree?
判断是不是树的条件;
1.除了根节点剩下的入度都是1;(有向)
2.只有一个根节点;
3.空树也算,环不能算,包括自环;

//以后保证再不改代码了,越改越乱qwq
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
int p[100005], success;
int vis[100005];
int d[100005];
int edge;
int lst;
int Case=1;
void init() {
    for (int i = 0; i < 100005; i++) {
        p[i] = i;
        vis[i]=0;
        d[i]=0;
    }
}
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}
void merge(int x, int y) {
    int px = find(x);
    int py = find(y);
    if(x==y) success=0;
    else if (px != py and !d[y]) {
        // s[px]+=s[py];
        p[py] = px;
        d[y]=1;
    } else
        success = 0;
}
bool check(){
    int sum=0;
    for(int i=1;i<=100005;i++){
        if(find(i)==i and vis[i]) sum++;
    }
    
    return sum==1;
}
int main() {
    int n, m, a, b;
    while (scanf("%d %d", &n, &m) != EOF) {
        
        if (m == 0 and n == 0) {
            printf("Case %d is a tree.\n",Case++);
            continue;
        }
        init();

        if (m == -1 and n == -1) {
            break;
        }
        success = 1;
        merge(n, m);
        edge=1;
        
        vis[n] =  vis[m] = 1;
        while (scanf("%d %d", &a, &b) , a and b) {
            merge(a, b);
            lst=a;
            vis[a] = 1, vis[b] = 1;
            edge++;
        }
        
        printf("Case %d ",Case++);
        if (success == 0) {
            printf("is not a tree.\n");
            
        } else {
            if (check()) {
                printf("is a tree.\n");
            } else {
                printf("is not a tree.\n");
            }
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值