原题地址
http://acm.hdu.edu.cn/showproblem.php?pid=1232
题意:有N个城镇,已经现在已经有M条道路,每条道路连接两个城镇(可以重复连接),目标是使任意两个城镇间都可以实现交通(不一定有直接的道路相连,只要互相间接通过道路可达即可),求最少还需要建设多少条道路。
解题思路
本题看上去像图的连通性问题,和图论有关,但是其实不必用图论的那些算法解决。(说这话是因为这是并查集配的练习…)
并查集是一种用来管理元素分组情况的数据结构,主要用于处理一些不相交集合的合并问题。常见的有求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。
基本的并查集结构由一个整数型的数组和两个函数构成。数组pre[]记录了每个点的前导点是什么,函数find是查找,join(unite)是合并。
传送一下大神们的总结:
说回这道题,用并查集表示每个城镇的连通情况,具有相同根节点(同一组)的节点视为有道路连通,构造好原始的M条道路后,只需判断结点1和其他结点 i 是否在同一子集里,如果不在同一组,则新建一条道路并合并1和 i 。(方法不唯一)
AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1005;
int n, pre[maxn];
void init() //每个结点单独成树
{
for (int i = 1; i <= n; ++i)
pre[i] = i;
}
int find_root(int x) //找到x所在集合的根节点
{
int r = x;
while (pre[r] != r) //还没找到根节点r
r = pre[r];
//并查集的路径压缩,沿路的结点直接上级都改为根节点
int i = x, j;
while (i != r)
{
j = pre[i]; //在改变i的上级之前用j记录i的上级
pre[i] = r; //改变i的上级为根节点
i = j; //从i原来的上级起继续搜索
}
return r;
}
void unite(int x, int y) //合并x和y所在子集
{
int fx = find_root(x), fy = find_root(y); //找到各自的根
if (fx == fy) return;
pre[fy] = fx;
}
int main()
{
ios::sync_with_stdio(false);
int m, t1, t2, ans;
while (cin >> n && n)
{
ans = 0;
init(); //初始化
cin >> m;
for (int i = 0; i < m; ++i) //建立已有连通
{
cin >> t1 >> t2;
unite(t1, t2);
}
int first = find_root(1), tmp;
for (int i = 2; i <= n; ++i) //直到1能连接所有结点
{
tmp = find_root(i);
if (tmp != first) //1和i不属于一个集合
{
ans++;
pre[tmp] = first; //新增一个通路
}
}
cout << ans << endl;
}
return 0;
}
算法复杂度:O(mα(n)),这里的 α 是 Ackerman 函数的某个反函数,只需知道是一个很小的数就可以了。
耗时:46ms