HDU 1232——畅通工程 并查集入门题(附带并查集板子)

  • 题目链接 :http://acm.hdu.edu.cn/showproblem.php?pid=1232
  • 题意 :给定你n个村庄以及m条路,每条路连接两个村庄,求还需要最少多少条路使得任意两个村庄之间互相可以到达(直接间接都可以)。
  • 思路 : 这是一道并查集经典的入门题,可以看这些村庄有多少个连通分量,然后连通分量的个数- 1即为答案
    在这里插入图片描述
    如图所示,这是第一个样例,节点1 3 4即为一个连通分量,2为一个连通分量,要想让任意两个村庄都能到达,只需要2 和 1 3 4任意一点连接即可。(图中的方向只是为了表示1 到 3 的路和3 到1的路不同,不过走起来是没有方向的。)
  • 代码 :
#include "bits/stdc++.h"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define fori(i,l,u) for(int i = l;i < u;i++)
#define forj(j,l,u) for(int j = l;j < u;j++)
#define pb push_back
#define mk make_pair
#define F first
#define S second
typedef long long  ll;
typedef pair<string, int > ps;
typedef pair<int, int> pi;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<pi> vpi;
const int maxn = 1e6 + 6;
ll N;
ll M;
ll u,v;
ll node[maxn];
ll ans = 0,visit[maxn];
ll find(ll x){              //寻找根节点坐标,因为init里初始 i = node[i]
    if (x == node[x]) {
        return x;
    }
    return find(node[x]);
}
void Unite(ll x,ll y){      //合并集合
    x = find(x);
    y = find(y);
    if (x == y) {
        return;
    }
    node[x] = y;
}
bool same(ll x,ll y){       //判断是不是同一集合,判断根节点的下标
    return find(x) == find(y);
}
void init(){                //初始化
    mem(visit,0);
    fori(i, 1, N+1){
        node[i] = i;
    }
}
//以上都是并查集的板子
int main()
{
//    freopen("1.txt", "r", stdin);
    while(~scanf("%lld%lld",&N,&M) && N){
        init();
        ans = 0;
        fori(i, 0, M){
            scanf("%lld%lld",&u,&v);
            if (!same(u, v)) {          //判断是不是同一个连通分量
                Unite(u, v);
            }
        }
        fori(i, 1, N+1){
            if (node[i] == i) {         //判断有多少个连通分量
                ans ++;
            }
        }
        ans -= 1;
        printf("%lld\n",ans);
    }
    return 0;
}


  • 遇到的问题 :这是第一道并查集的题,只需要把板子写上,有些理解就能做出来,不过我写的不是标准的板子,效率有点低,仔细学习并查集可以参考这个大佬的博客 :https://blog.csdn.net/luomingjun12315/article/details/47373345
    最后,一定要注释掉 freopen!!!不然听取WA声一片

  • 并查集的板子(将高度小的并到高度大的子树;初始化时连接到根节点)
    1)

const int maxn = 1e6 + 7;
int n,m;
int node[maxn],rk[maxn];
void init(){
    fori(i, 1, n+1){
        node[i] = i;
        rk[i] = 0;
    }
}
int find(int x){
    int t;
    if (x != node[x]) {
        t = node[x];
        node[x] = find(node[x]);
        rk[t] ++;
    }
    return node[x];
}
void merge(int x,int y){
    x = find(x);
    y = find(y);
    if (x == y) {
        return;
    }
    if (rk[x] < rk[y]) {
        node[x] = y;
    }
    else{
        node[y] = x;
    }
}

这个是通过 merge时调用find,在find里递归设置rk 高度。
2)

const int maxn = 1e6 + 7;
int n,m;
int node[maxn],rk[maxn];
void init(){
    fori(i, 1, n+1){
        node[i] = i;
        rk[i] = 0;
    }
}
int find(int x){
    if (x == node[x]) {
        return x;
    }
    return node[x] = find(node[x]);
}
void merge(int x,int y){
    x = find(x);
    y = find(y);
    if (x == y) {
        return;
    }
    if (rk[x] < rk[y]) {
        node[x] = y;
    }
    else{
        node[y] = x;
        if (rk[x] == rk[y]) {
            rk[x] ++;
        }
    }
}

以上两种都一样。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值