【u015】兽径管理
【问题描述】
约翰农场的牛群希望能够在 N 个(1<=N<=200)草地之间任意移动。草地的编号由 1到N。草地之间有树林隔开。牛群希望能够选择草地间的路径,使牛群能够从任一 片草地移动到任一片其它草地。 牛群可在路径上双向通行。
牛群并不能创造路径,但是他们会保有及利用已经发现的野兽所走出来的路径(以 下简称兽径)。每星期他们会选择并管理一些或全部已知的兽径当作通路。
牛群每星期初会发现一条新的兽径。他们接着必须决定管理哪些兽径来组成该周牛 群移动的通路,使得牛群得以从任一草地移动到任一草地。牛群只能使用当周有被 管理的兽径做为通路。
牛群希望他们管理的兽径长度和为最小。牛群可以从所有他们知道的所有兽径中挑 选出一些来管理。牛群可以挑选的兽径与它之前是否曾被管理无关。
兽径决不会是直线,因此连接两片草地之间的不同兽径长度可以不同。 此外虽然 两条兽径或许会相交,但牛群非常的专注,除非交点是在草地内,否则不会在交点 换到另外一条兽径上。
在每周开始的时候,牛群会描述他们新发现的兽径。如果可能的话,请找出可从任 何一草地通达另一草地的一组需管理的兽径,使其兽径长度和最小。
【输入文件】
输入的第一行包含两个用空白分开的整数 N 和 W。W 代表你的程序需要处理 的周数. (1 <= W <= 6000)。
以下每处理一周,读入一行数据,代表该周新发现的兽径,由三个以空白分开 的整数分别代表该兽径的两个端点 (两片草地的编号) 与该兽径的长度(1…10000)。一条兽径的两个端点一定不同。
【输出文件】
每次读入新发现的兽径后,你的程序必须立刻输出一组兽径的长度和,此组兽径可从任何一草地通达另一草地,并使兽径长度和最小。如果不能找到一组可从任一草地通达另一草地的兽径,则输出 “-1”。
【样例】
輸入 |
輸出 |
說明 |
4 6 |
|
|
1 2 10 |
|
|
|
-1 |
No trail connects 4 to the rest of the fields. |
1 3 8 |
|
|
|
-1 |
No trail connects 4 to the rest of the fields. |
3 2 3 |
|
|
|
-1 |
No trail connects 4 to the rest of the fields. |
1 4 3 |
|
|
|
14 |
Maintain 1 4 3, 1 3 8, and 3 2 3. |
1 3 6 |
|
|
|
12 |
Maintain 1 4 3, 1 3 6, and 3 2 3. |
2 1 2 |
|
|
|
8 |
Maintain 1 4 3, 2 1 2, and 3 2 3. |
|
program exit |
|
【题解】
题目说的什么到达其他草坪。翻译就是要求最小生成树。然后每周都增加一条路径。
1.是否联通。->用并查集。每遇到一个r1!=r2则接一条线。接了n-1条线。则整张图都联通了。否则输出-1即可。
2.最小生成树?用kruskal算法。因为是一条边一条边地插入。所以可以用选择排序。注意这种一个一个元素地插入到一个有序序列中每次操作的时间复杂度都为O(n).因为你最坏情况只要扫描一遍就可以了。然后扫描W次。总的复杂度为O(W^2)。
然后按照克鲁斯卡尔算法的方法一条边一条边地递增答案就好。
【代码】
#include <cstdio> struct bian { int x, y, z; }; int n, w,f[201],tot_bian =1,totm = 0,fa[201]; bian a[6001]; int findfather2(int x) //这个寻找根节点的int用于克鲁斯卡尔算法 { if (fa[x] == x) return x; if (fa[x] != x) fa[x] = findfather2(fa[x]);//路径压缩 return fa[x]; } int findfather(int x)//这个寻找根节点的函数用于判断图是否联通 { if (f[x] == x) return x; if (f[x] != x) f[x] = findfather(f[x]); return f[x]; } int main() { scanf("%d%d", &n, &w); for (int i = 1; i <= n; i++) f[i] = i; a[1].z = -210000000;//在序列的开始添加一个很小的数字。(实则没用) for (int i = 1; i <= w; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); int key = tot_bian + 1;//从tot_bian+1的位置开始扫描 while (a[key - 1].z > z)//一直往前扫描直到a[key-1]<=a[key] key--;//a数组是从小到大排序的。 for (int j = tot_bian + 1; j >= key + 1; j--)//把key和key后面的数字往后移动 a[j] = a[j - 1]; a[key].x = x;a[key].y = y;a[key].z = z;//把新输入的边放在key的位置。 tot_bian++;//边数递增 int r1 = findfather(x), r2 = findfather(y); if (r1 != r2)//如果能够添加有效边 {//有效边的含义是能够递增联通的点的数目的边 totm++;//边的数目递增 f[r1] = r2; } if (totm < n - 1)//如果添加的有效边的数目小于n-1则还没有联通 printf("-1\n"); else //如果已经联通了 { int now = 0,ans= 0; for (int j = 1; j <= n; j++)//初始化克鲁斯卡尔算法的并查集 fa[j] = j; for (int j = 2; j <= tot_bian; j++)//第一个元素是那个-210000000 { int r1 = findfather2(a[j].x), r2 = findfather2(a[j].y); if (r1 != r2)//从小到大选边即可。 { now++; ans += a[j].z; fa[r1] = r2; if (now == n - 1) break; } } printf("%d\n", ans); } } return 0; }