并查集练习

并查集练习

数据结构—并查集

A.畅通工程

题意:
n个城市,m条道路,问还需要多少条道路能将n个城市连起来(不必需要直接道路)

Sol:
根据m条道路,将n个城市进行合并操作,最终找出有多少个连通块,答案为联通块的数量减1.(n个城市连起来需要n-1条道路)

Code:

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

struct UNF{
    vector<int>p;
    void init(int n)
    {
        for(int i = 0; i <= n + 10; ++i) {
            p.push_back(i);
        }
    }
    int find(int x)
    {
        if(x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    void Union(int a, int b)
    {
        int pa = find(a);
        int pb = find(b);
        if(pa != pb) 
        {
            p[pa] = pb;
        }
    }
};

int main(){
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        if(!n) break;
        UNF city;
        city.init(n);
        while(m -- )
        {
            int x, y;
            scanf("%d%d", &x, &y);
            city.Union(x, y);
        }
        int cnt = 0; 
        for(int i = 1; i <= n; ++i)
            cnt += (city.p[i] == i);
        printf("%d\n", cnt - 1);
    }
    return 0;
}

C.The Suspects

题意:有n个人,m个团队,团队有k个人,问0所在的团队的人数

Sol:并查集维护联通块的大小

Code:

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;

struct UNF{
    vector<int>p;
    vector<int>f;
    void init(int n)
    {
        for(int i = 0; i <= n + 10; ++i) {
            p.push_back(i);
            f.push_back(1);
        }
    }
    int find(int x)
    {
        if(x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    void Union(int a, int b)
    {
        int pa = find(a);
        int pb = find(b);
        if(pa != pb) 
        {
            p[pb] = pa;
            f[pa] += f[pb];
        }
    }
};

int main(){
    int n, m;
    while(scanf("%d%d", &n, &m))
    {
        if(!n && !m) break;
        UNF team;
        team.init(n);
        while( m -- )
        {
            int k, x, y;
            scanf("%d", &k);
            for(int i = 1; i <= k; ++i)
            {
                if(i == 1) {
                    scanf("%d", &x);
                }
                else {
                    scanf("%d", &y);
                    team.Union(x, y);
                }
            }
        }
        printf("%d\n", team.f[team.find(0)]);
    }
    return 0;
}

B.Find them, Catch them

题意:有n个人,m次询问。
A 1 2 查询a和b是否是敌人或者是无法判断
D 1 2 a和b是敌人

Sol:
:扩展域并查集。假设1-n为第一个集团,n+1-2*n为第二个集团。
换句话说,敌人的敌人就是朋友。所以对于a和b是敌人进行的并查集合并为,将a和b+n合并以及a+n和b合并
:带权并查集,用d[i]表示与根节点的关系,0表示朋友,1表示敌人
Code:

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;

struct UNF{
    vector<int>p;
    void init(int n)
    {
        for(int i = 0; i <= n + 10; ++i) {
            p.push_back(i);
        }
    }
    int find(int x)
    {
        if(x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    void Union(int a, int b)
    {
        int pa = find(a);
        int pb = find(b);
        if(pa != pb) 
        {
            p[pa] = pb;
        }
    }
};


int main(){
    int T; scanf("%d", &T);
    while ( T -- ){
        int n, m;
        scanf("%d%d", &n, &m);
        UNF gangs;
        gangs.init(2 * n);
        while( m -- )
        {
            char op[2];
            int a, b;
            scanf("%s%d%d", op, &a, &b);
            if(*op == 'D') {
                gangs.Union(a, b + n);
                gangs.Union(a + n, b);
            }
            else {
                int pa = gangs.find(a);
                int pb = gangs.find(b);
                if(pa == pb) {
                    puts("In the same gang.");
                }
                else if(pa == gangs.find(b + n)) {
                    puts("In different gangs.");
                }
                else {
                    puts("Not sure yet.");
                }
            }
        }
    }
    return 0;
}

D.食物链

题意:有一个食物链,A->B, B->C, C->A。现在有n个动物,编号为1-n,每个动物都是ABC的一种,但是不知道是哪一种,
现在有人用两种说法对这n个动物构成的食物链进行描述。
- 第一种说法:1 X Y 表示X和Y是同一类
- 第二种说法:2 X Y 表示X吃Y
这个人说了K句话,问有多少句话是假话。假话满足一下条件之一,否则就是真话
- 1) 当前的话与前面的某些真的话冲突,就是假话;
- 2) 当前的话中X或Y比N大,就是假话;
- 3) 当前的话表示X吃X,就是假话。

Sol:带权并查集。d[i]表示与根节点的关系,0表示同类,1表示i吃根节点,2表示根节点吃i

Code:

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
// 食物链
// 带权并查集
struct UNF{
    vector<int>p;
    vector<int>d;
    void init(int n)
    {
        for(int i = 0; i <= n + 10; ++i) {
            p.push_back(i);
            d.push_back(0);
        }
    }
    int find(int x)
    {
        if(x != p[x]) {
            int t = p[x];
            p[x] = find(p[x]);
            d[x] = (d[x] + d[t]) % 3;
        }
        return p[x];
    }
    void Union(int a, int b)
    {
        int pa = find(a);
        int pb = find(b);
        if(pa != pb) 
        {
            p[pa] = pb;
        }
    }
};

int main(){
    int n,  k;
    scanf("%d%d", &n, &k);
    UNF ch;
    ch.init(n);
    int d, x, y;
    int cnt = 0;
    while( k -- )
    {
        scanf("%d%d%d", &d, &x, &y);
        if(x > n || y > n || (x == y && d == 2)) ++ cnt;
        else {
            int px = ch.find(x);
            int py = ch.find(y);
            if(px != py) {
                ch.p[px] = py;
                ch.d[px] = (ch.d[y] - ch.d[x] + d - 1) % 3;
            }
            else {
                -- d;
                if(d != (ch.d[x] - ch.d[y] + 3) %  3) cnt ++;
            }
        }
    }
    cout << cnt << endl;
    return 0;
}

E.Rochambeau

题意:有n个人在玩石头剪刀布,然后有m个回合。其中一个是裁判,其余人分成a,b,c三组,同组的人只会出一个手势。
问你是否能找到这个裁判,如果能找到输出最早能在在第几回合能找到
如果有多个,输出"Can not determine"
如果不存在,输出"Impossible"

Sol:首先分组可以由并查集维护,同上题食物链一样,d[i]表示与根节点的关系,0表示同组,1表示输给根节点,2表示赢根节点
然后去枚举n个人作为裁判时的情况。如果有cnt个人符合
- cnt > 1 无法确定
- cnt == 0 不存在
- cnt == 1 存在唯一裁判,那么就是求最早在第几回合能判断出。我们只需找出其他人矛盾的最大回合。

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;

struct UNF{
    vector<int>p;
    vector<int>d;
    void init(int n)
    {
        for(int i = 0; i <= n + 10; ++i) {
            p.push_back(i);
            d.push_back(0);
        }
    }
    int find(int x)
    {
        if(x != p[x]) {
            int t = p[x];
            p[x] = find(p[x]);
            d[x] = (d[x] + d[t]) % 3;
        }
        return p[x];
    }
};

struct node{
    int a, b;
    int c;
}A[2020];

int idx;
int n, m;

bool judge(int u)
{
    UNF ch;
    ch.init(n);
    for(int i = 1; i <= m; ++i)
    {
        int x = A[i].a;
        int y = A[i].b;
        int d = A[i].c;
        if(x == u || y == u) continue;
        int fx = ch.find(x);
        int fy = ch.find(y);
        if(fx == fy)
        {
            if(d != (ch.d[x] - ch.d[y] + 3) % 3) {
                idx = max(idx, i);
                return false;
            }
        }
        else {
            int px = ch.find(x);
            int py = ch.find(y);
            ch.p[px] = py;
            ch.d[px] = (ch.d[y] - ch.d[x] + d + 3) % 3;
        }
    }
    return true;
}

int main(){
    while(scanf("%d%d", &n, &m) != EOF)
    {
        for(int i = 1; i <= m; ++i)
        {
            int a, b;
            char ch;
            scanf("%d%c%d", &a, &ch, &b);
            if(ch == '=') A[i] = {a, b, 0};
            else if(ch == '<') A[i] = {a, b, 1};
            else A[i] = {a, b, 2};
        }
        idx = 0;
        int ans = 0;
        int cnt = 0;
        for(int i = 0; i < n; ++i)
        {
            if( judge(i) ) {
                ++ cnt;
                ans = i;
            }
        }  
        if(cnt==1) printf("Player %d can be determined to be the judge after %d lines\n", ans, idx);
        else if(cnt > 1) printf("Can not determine\n");
        else printf("Impossible\n");
    }
    return 0;
}

update 2021/12/19

Other 用法

Educational Codeforces Round 119 E

题意:
q q q次次操作

  • 1 x 向数组后面插入 x x x
  • 2.x y 将数组中所有 x x x该为 y y y

sol
很容易想到用并查集来维护这些值变成了什么,但是当我们正着去做的时候,后面如果要改变前面已经该过了的值话就会出问题。比如前面你要把1变成2,后面又让1变成3,那么这样操作下来的话,最后1都会变成3 。那要怎么避免呢,我们将操作保存下来;然后反着去维护即可。具体细节看代码

Code


#include <iostream>
using namespace std;
const int N = 5e5 + 10;
int p[N];
int opt[N], x[N], y[N];

void solve()
{
    int q;
    cin >> q;
    rep(i, 1, 5e5) p[i] = i;
    for (int i = 1; i <= q; ++ i) {
        cin >> opt[i] >> x[i];
        if(opt[i] == 2) {
            cin >> y[i];
        }
    } 
    vector<int> res;
    for(int i = q; i ; i -- ) {
        if(opt[i] == 1) res.push_back(p[x[i]]);
        else {
            p[x[i]] = p[y[i]];
        }
    }
    reverse(ALL(res));
    for(int x : res) cout << x << " ";
}

signed main(){

    
    int T = 1;
    // cin >> T;
    while(T --) solve();
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

W⁡angduoyu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值