HDU 1232 畅通工程(并查集)

来源:互联网 发布:美国gdp数据公布网站 编辑:程序博客网 时间:2024/06/06 05:03

原题地址

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

0 0