读者可以先阅读并查集相关知识点
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;
}