题解 P2872 【[USACO07DEC]道路建设Building Roads】
这道题真的是令人窒息,Kruskal调了贼久一直RE,最后发现数组大小稍微少了那么一点点。(也就10倍吧。。)
言归正传,根据本人的分析(以及算法标签的提示),这是一道求最小生成树的题目,当然要注意已经有一些路被建成了,因此他们直接标0即可。
下面是这道题用到了的所有(全局)变量。
maxn, n, m就不解释了。
x[]和y[]是用来储存农场的坐标的,当然也可以用二维数组写,只是我懒得敲那么多字(说起来差别也不大)。
f是并查集中储存祖先的数组。
如果有不了解的并查集的可以参考这一片讲解,个人认为十分通俗易懂。 传送门
f1和f2是后面暂时储存有道路连接的农场的变量。
top是Kruskal主体中记录最顶层的。
cnt是记录长度的。
ans我觉得也是废话(耶,皮这一下我很开心)。
1 const int maxn = 1000001; 2 3 int n, m; 4 int x[maxn], y[maxn], f[maxn], f1, f2; 5 int top = 0, cnt = 0; 6 double ans = 0;
接下来是储存两点距离的结构体,以及结构体排序。
1 struct node { 2 int x, y; 3 double val; 4 }dis[maxn]; 5 6 bool cmp(node a, node b) { 7 if(a.val == b.val) 8 return a.x < b.x; 9 return a.val < b.val; 10 }
以及并查集模板(其中find函数使用了路径压缩)
1 int find(int x) { 2 int r = x; 3 while(r != f[r]) r = f[r]; 4 int i = x, j; 5 while(f[i] != r) { 6 j = f[i]; 7 f[i] = r; 8 i = j; 9 } 10 return r; 11 } 12 13 void merge(int x, int y) { 14 x = find(x); 15 y = find(y); 16 if(x != y) f[y] = x; 17 }
偶对了还有最没用的函数dt,用于求两点距离。
1 double dt(int x1,int x2,int y1,int y2) { 2 return sqrt(pow(double(x1 - x2), 2) + pow(double(y1 - y2), 2)); 3 }
接下来果断开始主函数part。
读入数据,别忘了初始化并查集。
1 cin >> n >> m; 2 for(int i = 1; i <= n; i++) cin >> x[i] >> y[i]; 3 for(int i = 1; i <= n; i++) f[i] = i; 4 for(int i = 1; i <= n; i++) 5 for(int j = i + 1; j <= n; j++) { 6 dis[++cnt].x = i; 7 dis[cnt].y = j; 8 dis[cnt].val = dt(x[i], x[j], y[i], y[j]); 9 } 10 for(int i = 1; i <= m; i++) { 11 cin >> f1 >> f2; 12 dis[++cnt].x = f1; 13 dis[cnt].y = f2; 14 dis[cnt].val = 0; 15 }
然后给dis排个序。
1 sort(dis + 1, dis + cnt + 1, cmp);
最重要的部分:Kruskal模板
Kruskal算法将图中的每一个顶点视为一个独立的集合,首先将图中所有的边按权值大小从小到大排列,接着按顺序选择每一条边,如果这条边的两个端点不属于同一个集合,那么将它们所在的集合合并,同时将这条边加入E’。直到所有的顶点都属于同一个集合时,E’就是一颗最小生成树。
--摘抄自《ACM国际大学生程序设计竞赛 知识与入门》
1 for(int i = 1; i <= cnt; i++) { 2 if(find(dis[++top].x) != find(dis[top].y)) { 3 ans += dis[top].val; 4 merge(dis[top].x, dis[top].y); 5 } 6 }
最后,愉快的输出结果就好了,别忘了要保留两位小数。