畅通工程(并查集)

原题链接

读者可以先阅读并查集相关知识点

Problem Description

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

Input

测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的。当N为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最少还需要建设的道路数目。

Sample Input

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

Sample Output

1
0
2
998

思路:

  • 本题为典型的并查集板子题
  • 只需要计算出有多少个集合,集合数量减1就是最少还需要建设的道路数目

举例:
假设有N = 5个城市,编号分别为1 2 3 4 5
然后输入三对数据表示联通的道路
1 2
3 4
2 5
输入1 2之后,城市联通情况为1—2 3 4 5
输入3 4之后,城市联通情况为1—2 3—4 5
输入2 5之后,城市联通情况如图:1—2 3—4 2—5
在这里插入图片描述
不难发现,输入所有的数据之后,图中有两个集合

  • 1—2—5
  • 3—4

且只需要连接3—5一条道路,就可以使所有城市互通

AC代码:

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

using namespace std;

const int N = 1010;
int Set[N];

//并查集:查找树的'根'
int findx(int x)
{
    int r = x;
    while (r != Set[r])
    {
        r = Set[r];
    }
    return r;
}

//并查集:合并操作
void merge(int x,int y)
{
    int a = findx(x);
    int b = findx(y);
    if (a != b) Set[a] = b;
}

int main()
{
    int n,m;
    while (cin >> n >> m && n)
    {
        for (int i = 1;i <= n;i++) 
        {
            //初始化并查集
            Set[i] = i;
        }
        
        //m次循环
        for (int i = 0; i < m;i++)
        {
            int x;
            int y;
            cin >> x >> y;
            
            merge(x,y);
        }
        
        int cnt = 0;//cnt表示集合的数量
        for (int i = 1;i <= n;i++)
        {
            if (Set[i] == i) cnt++;
        }
        
        cout << cnt - 1 << endl;
    }
    return 0;
    
}

最后,再以样例

4 2
1 3
4 3

模拟一下解题过程:n = 4,m = 2

  • 首先初始化并查集,Set[ i ]表示父节点(i节点的‘BOSS’),如图所示。Set[ i ] = i表示初始化时每一个节点都是一个独立的集合在这里插入图片描述
  • 紧接着进行两次循环,每次输入一对数据,表示两个城市互通。当输入1—3时。调用merge()函数,对1和3两个集合进行合并。在merge()函数里,会首先调用findx()函数,找到1集合和3集合的根节点。由于初始时他们都是独立的集合,即 i 的根节点就是Set[ i ],所以输入1-3后,a = 1,b = 3。接着执行Set[ 1 ] = 3。在这里插入图片描述
  • 当输入4—3时。再去调用merge()函数,对4和3两个集合进行合并。在merge()函数里,同样的,会首先调用findx()函数,找到4集合和3集合的根节点。由于初始时4集合是独立的集合,即 4 的根节点就是Set[ 4 ],3的根节点就是Set[ 3 ],所以输入4-3后,a = 4,b = 3。接着执行Set[ 4 ] = 3。在这里插入图片描述
    在此总结一下,Set[a] = b就是改变根/父节点的操作。
  • 到此,如果画出并查集的树形结构,会发现节点1和节点4的父节点是节点3,如图所示:在这里插入图片描述
  • 此时,并查集中有两个集合:
    ①集合1—3—4
    ②集合2
  • 最后,使用if (Set[i] == i) cnt++;查询集合的个数,只有Set[3] == 3和Set[2] == 2满足条件,即只有2节点和3节点是父/根节点,故集合的个数为2
  • 打印答案,集合的个数减1就是还需要建设的道路数目。

AC代码2-使用路径压缩:

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

using namespace std;

const int N = 1010;
int Set[N];

//查找根节点 + 路径压缩
int find(int x)
{
    if (x != Set[x])
    {
        Set[x] = find(Set[x]);
    }
    return Set[x];
}

//合并集合
void merge(int x,int y)
{
    int a = find(x);//查找x的根节点
    int b = find(y);//查找y的根节点
    if (a != b) Set[a] = b;//合并
}

int main()
{
    int n,m;
    while (cin >> n >> m && n)
    {
        for (int i = 1;i <= n;i++) Set[i] = i;//初始化并查集
        
        int x,y;
        for (int i = 0;i < m;i++)
        {
            cin >> x >> y;
            merge(x,y);
        }
        
        int cnt = 0;
        for (int i = 1;i <= n;i++)
        {
            if (Set[i] == i) cnt++;
        }
        
        cout << cnt - 1 << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值