HDU #1863 畅通工程 MST kruskal 贪心+并查集
Description
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。Input测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N
行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。
Output对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。
Sample Input3 3 1 2 1 1 3 2 2 3 4 1 3 2 3 2 0 100Sample Output
3 ?
思路
这道题是求MST的边权之和的裸题,比较简单,用来练习并查集+ kruskal 比较好,可以当模板。
做完这道题学习到了一个小技巧:想要对并查集进行优化,并不需要两个数组(一个存储父结点、一个存储集合元素个数)去实现,而用一个数组去实现就好了,怎么实现呢?很简单,将数组元素的值初始化为 -1 ,当执行 Union-Set 操作的时候,采用降秩合并,将元素较少的根树的根结点指向元素多的根树的根结点,实现合并操作,并且元素多的根树的根结点在数组里的值修改为两根结点在数组里的值之和。这个操作非常亮眼,它利用了相加后的负值来表示集合元素个数,如果一个根结点的值小于另一个根结点的值,说明前一颗根树中的元素个数要比后一个根树的元素个数要多。
AC代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<iterator> using namespace std; #define INF 0x3f3f3f3f #define MAXM 105 #define MAXN 10920 int n, m; //n 边数,m 顶点数 struct Edge{ int from; int to; int w; }e[MAXN]; bool cmp(const Edge& a, const Edge& b ) { return a.w < b.w; } //并查集相关操作 int p[MAXM]; int findSet (int i) { int tmp; //查找 i 的根 for (tmp = i; p[tmp] >= 0; tmp = p[tmp]); //压缩路径 while (tmp != i) { int t = p[i]; p[i] = tmp; i = t; } return tmp; } void unionSet (int a, int b) { int r1 = findSet(a), r2 = findSet(b); int tmp = p[r1] + p[r2]; //-tmp表示两集合结点数之和 if (p[r1] < p[r2]) { p[r2] = r1; p[r1] = tmp; } else { p[r1] = r2; p[r2] = tmp; } } void kruskal () { memset(p, -1, sizeof(p)); //将每个顶点都初始化为一个独立的集合 sort(e, e + n, cmp); int sum = 0,cnt = 1; //sum统计MST的权值之和; cnt统计加入树集的边,判断能否构成MST for (int i = 0; i < n; i++) { int u = e[i].from, v = e[i].to; if (findSet(u) != findSet(v)) { unionSet(u, v); sum += e[i].w; cnt++; } } if (cnt != m) printf("?\n"); else printf("%d\n", sum); } int main(void) { while (scanf("%d%d", &n, &m) && n) { for (int i = 0; i < n; i++) scanf ("%d%d%d", &e[i].from, &e[i].to, &e[i].w); kruskal(); } return 0; }
————全心全意投入,拒绝画地为牢