1.The Unique MST
题目
- 总时间限制:
- 1000ms
- 内存限制:
- 65536kB
- 描述
- Given a connected undirected graph, tell if its minimum spanning tree is unique.
Definition 1 (Spanning Tree): Consider a connected, undirected graph G = (V, E). A spanning tree of G is a subgraph of G, say T = (V', E'), with the following properties:
1. V' = V.
2. T is connected and acyclic.
Definition 2 (Minimum Spanning Tree): Consider an edge-weighted, connected, undirected graph G = (V, E). The minimum spanning tree T = (V, E') of G is the spanning tree that has the smallest total cost. The total cost of T means the sum of the weights on all the edges in E'. - 输入
- The first line contains a single integer t (1 <= t <= 20), the number of test cases. Each case represents a graph. It begins with a line containing two integers n and m (1 <= n <= 100), the number of nodes and edges. Each of the following m lines contains a triple (xi, yi, wi), indicating that xi and yi are connected by an edge with weight = wi. For any two nodes, there is at most one edge connecting them.
- 输出
- For each input, if the MST is unique, print the total cost of it, or otherwise print the string 'Not Unique!'.
- 样例输入
-
2 3 3 1 2 1 2 3 2 3 1 3 4 4 1 2 2 2 3 2 3 4 2 4 1 2
- 样例输出
-
3 Not Unique!
- 来源
POJ Monthly--2004.06.27 srbga@POJ
我的代码
1 # include <iostream>
2 # include <algorithm>
3 using namespace std;
4
5 typedef struct{
6 int x, y, w;
7 } EDGE;
8
9 bool cmp(EDGE e1, EDGE e2){
10 return e1.w < e2.w;
11 }
12
13 EDGE * e; //按权重增序存储所有边的信息
14 int * f; //父节点表示法
15 int T, N, M,//输入参数
16 sum; //最小生成树的边权和
17
18 //带路径压缩的根节点搜索算法
19 int FindRoot(int i){
20 if(f[i] != i)
21 f[i] = FindRoot(f[i]);
22 return f[i];
23 }
24
25 //判断最小生成树是否唯一
26 bool IsUnique(){
27 int cnt1 = 0; //可选的加入最小生成树的边的数量
28 int cnt2 = 0; //实际上加入最小生成树的边的数量
29 int i = 0, j = 0;
30 int fx, fy;
31 while(i < M){
32 j = i;
33 while(e[i].w == e[j].w && j < M){
34 fx = FindRoot(e[j].x);
35 fy = FindRoot(e[j].y);
36 if(fx != fy)
37 cnt1++; //计数:边j可以加入最小生成树
38 j++;
39 }
40 j = i;
41 while(e[i].w == e[j].w && j < M){
42 fx = FindRoot(e[j].x);
43 fy = FindRoot(e[j].y);
44 if(fx != fy){
45 cnt2++; //计数:边j实际加入最小生成树
46 f[fx] = fy; //合并以fx和fy为根的树(等价类)
47 sum += e[j].w; //计算边权和
48 }
49 j++;
50 }
51 i = j;
52 }
53 return cnt1 == cnt2;
54 }
55
56 //等价类森林初始化
57 void ForestIni(){
58 cin >> N >> M;
59 sum = 0;
60 e = new EDGE[M+1];
61 f = new int[N+1];
62 for(int i=0; i<M; ++i)
63 cin >> e[i].x >> e[i].y >> e[i].w;
64 sort(e, e+M, cmp);
65 for(int i=1; i<=N; ++i)
66 f[i] = i;
67 }
68
69 //等价类森林删除
70 void ForestDel(){
71 delete [] e;
72 delete [] f;
73 }
74
75 int main(){
76 cin >> T;
77 while(T--){
78 ForestIni();
79 if(IsUnique())
80 cout << sum << endl;
81 else
82 cout << "Not Unique!\n";
83 ForestDel();
84 }
85 return 0;
86 }
实现思路
思路1:MST的可选边多于实际MST的边数 <=> MST不唯一(我的代码采用这个思路)
思路2:构造MST,删除其中一条边,重新构造MST,构造成功 <=> MST不唯一(参见https://blog.csdn.net/libin56842/article/details/17339525)
MST的构造方法
1.Prim算法
任选起始顶点加入树,寻找一顶点在树中&&另一顶点不在树中的权重最小的边,扩展树;重复上述过程,直至所有顶点加入树。
2.Kruskal算法
为顶点划分等价类,初始状态下每个顶点一个等价类;寻找两顶点不在同一等价类中的权重最小的边,合并等价类;重复上述过程,直至所有节点合并为一个等价类。
2.兔子与星空
题目
- 总时间限制:
- 1000ms
- 内存限制:
- 10000kB
- 描述
-
很久很久以前,森林里住着一群兔子。兔子们无聊的时候就喜欢研究星座。如图所示,天空中已经有了n颗星星,其中有些星星有边相连。兔子们希望删除掉一些边,然后使得保留下的边仍能是n颗星星连通。他们希望计算,保留的边的权值之和最小是多少?
- 输入
- 第一行只包含一个表示星星个数的数n,n不大于26,并且这n个星星是由大写字母表里的前n个字母表示。接下来的n-1行是由字母表的前n-1个字母开头。最后一个星星表示的字母不用输入。对于每一行,以每个星星表示的字母开头,然后后面跟着一个数字,表示有多少条边可以从这个星星到后面字母表中的星星。如果k是大于0,表示该行后面会表示k条边的k个数据。每条边的数据是由表示连接到另一端星星的字母和该边的权值组成。权值是正整数的并且小于100。该行的所有数据字段分隔单一空白。该星星网络将始终连接所有的星星。该星星网络将永远不会超过75条边。没有任何一个星星会有超过15条的边连接到其他星星(之前或之后的字母)。在下面的示例输入,数据是与上面的图相一致的。
- 输出
- 输出是一个整数,表示最小的权值和
- 样例输入
-
9 A 2 B 12 I 25 B 3 C 10 H 40 I 8 C 2 D 18 G 55 D 1 E 44 E 2 F 60 G 38 F 0 G 1 H 35 H 1 I 35
- 样例输出
-
216
- 提示
- 考虑看成最小生成树问题,注意输入表示。
我的代码
1 # include <iostream>
2 # include <algorithm>
3 using namespace std;
4
5 //边类
6 typedef struct {
7 int x, y, w;
8 } EDGE;
9 //比较边权值
10 bool cmp(EDGE e1, EDGE e2){
11 return e1.w < e2.w;
12 }
13 EDGE e[80]; //e[i]存储边i的信息
14 int f[30]; //f[i]存储节点i的父节点下标
15 int n, m; //结点数量n,边的数量m
16 int sum = 0;//最小生成树的边权和
17 //带路径压缩的根节点搜索算法
18 int FindRoot(int i){
19 if(f[i] != i)
20 f[i] = FindRoot(f[i]);
21 return f[i];
22 }
23 //Kruskal方法找最小生成树
24 void Kruskal(){
25 int i = 0, fx, fy, cnt = 1;
26 //边i<m未检测 或 合并等价类操作只进行了cnt<n次
27 while(i < m && cnt < n){
28 fx = FindRoot(e[i].x);
29 fy = FindRoot(e[i].y);
30 //合并以fx和fy为根的等价类等价类
31 if(fx != fy){
32 f[fx] = fy;
33 sum += e[i].w;
34 ++cnt; //记录合并等价类操作的次数
35 }
36 ++i;
37 }
38 }
39 //图的输入和初始化
40 void BuildGraph(){
41 int x, edge, w;
42 char xc, yc;
43 cin >> n; //共有n个节点
44 //初始化f[],每个等价类中只有一个元素,元素的父节点为自身
45 for(x = 0; x < n; ++x)
46 f[x] = x;
47 //初始化e[],记录边的数量m
48 for(x = 0, m = 0; x < n-1; ++x){
49 //输入第x个节点的信息
50 cin >> xc >> edge;
51 for(int j = 0; j < edge; ++j, ++m){
52 //输入边的信息
53 cin >> yc >> w;
54 e[m].x = x;
55 e[m].y = yc-'A';
56 e[m].w = w;
57 }
58 }
59 sort(e, e+m, cmp); //按边权值增序排列
60 }
61
62 int main(){
63 BuildGraph();
64 Kruskal();
65 cout << sum << endl;
66 return 0;
67 }
3.舰队、海域出击!
题目
总时间限制:
- 5000ms
- 单个测试点时间限制:
- 2000ms
- 内存限制:
- 262144kB
- 描述
-
作为一名海军提督,Pachi将指挥一支舰队向既定海域出击!
Pachi已经得到了海域的地图,地图上标识了一些既定目标和它们之间的一些单向航线。如果我们把既定目标看作点、航线看作边,那么海域就是一张有向图。不幸的是,Pachi是一个会迷路的提督QAQ,所以他在包含环(圈)的海域中必须小心谨慎,而在无环的海域中则可以大展身手。
受限于战时的消息传递方式,海域的地图只能以若干整数构成的数据的形式给出。作为舰队的通讯员,在出击之前,请你告诉提督海域中是否包含环。例如下面这个海域就是无环的:
而下面这个海域则是有环的(C-E-G-D-C):
- 输入
- 每个测试点包含多组数据,每组数据代表一片海域,各组数据之间无关。
第一行是数据组数T。
每组数据的第一行两个整数N,M,表示海域中既定目标数、航线数。
接下来M行每行2个不相等的整数x,y,表示从既定目标x到y有一条单向航线(所有既定目标使用1~N的整数表示)。
描述中的图片仅供参考,其顶点标记方式与本题数据无关。
1<=N<=100000,1<=M<=500000,1<=T<=5
注意:输入的有向图不一定是连通的。 - 输出
- 输出包含T行。
对于每组数据,输出Yes表示海域有环,输出No表示无环。 - 样例输入
-
2 7 6 1 2 1 3 2 4 2 5 3 6 3 7 12 13 1 2 2 3 2 4 3 5 5 6 4 6 6 7 7 8 8 4 7 9 9 10 10 11 10 12
- 样例输出
-
No Yes
- 提示
- 输入中的两张图就是描述中给出的示例图片。
我的代码
1 # include <iostream>
2 using namespace std;
3
4 //有向边类
5 typedef struct{
6 int x, y; //始点x,终点y
7 bool v; //是否被添加到DFS-tree中
8 } EDGE;
9 //节点类
10 typedef struct{
11 int f; //记录DFS-tree中的父节点下标
12 bool v; //是否被添加到DFS-tree中
13 } NODE;
14 EDGE * e; //e[]记录有向边信息
15 NODE * n; //f[]记录节点信息
16 int T, N, M;//数据组数T,节点数N,有向边数M
17
18 //输入并建立图
19 void BiuldGraph(){
20 cin >> N >> M;
21 e = new EDGE[M + 1];
22 n = new NODE[N + 1];
23 //边信息e[]初始化
24 for(int i = 0; i < M; ++i){
25 cin >> e[i].x >> e[i].y;
26 e[i].v = 0;
27 }
28 //节点信息n[]初始化
29 for(int i = 1; i <= N; ++i){
30 n[i].f = i;
31 n[i].v = 0;
32 }
33 }
34 //清除图
35 void DeleGraph(){
36 delete [] e;
37 delete [] n;
38 }
39 //深度优先遍历图中所有节点
40 //s是当前搜索的起始节点
41 void DFS(int s){
42 if(n[s].v) return; //已经访问过
43 n[s].v = 1; //标记节点s为已访问过
44 //找节点i的未访问过的邻边j
45 for(int j = 0; j < M; ++j)
46 if(!e[j].v && e[j].x == s && !n[e[j].y].v){
47 e[j].v = 1; //边j加入DFS-tree
48 n[e[j].y].f = s;//边j的终点y的父节点设置为节点s
49 DFS(e[j].y); //递归地深度优先搜索节点y
50 }
51 }
52 //判断在DFS-tree中,节点x是否是节点y的子孙
53 bool IsDescend(int x, int y){
54 //节点x是根节点
55 if(n[x].f == x)
56 return false;
57 //节点x的父节点是节点y
58 if(n[x].f == y)
59 return true;
60 //判断节点x的父节点是否是节点y的子孙
61 return IsDescend(n[x].f, y);
62 }
63
64 //寻找backward-edge,找到则说明有环
65 bool FindBackwardEdge(){
66 for(int j = 0; j < M; ++j)
67 //寻找图中未加入DFS-tree的边
68 if(!e[j].v)
69 //判断backward
70 if(IsDescend(e[j].x, e[j].y))
71 return true;
72 //未找到backward-edge
73 return false;
74 }
75
76 int main(){
77 cin >> T;
78 while(T--){
79 //建立图
80 BiuldGraph();
81 //深度优先遍历图,建立DFE-tree
82 for(int i = 1; i <= N; ++i)
83 DFS(i);
84 //寻找backward-edge,找到则说明有环
85 if(FindBackwardEdge())
86 cout << "Yes\n";
87 else
88 cout << "No\n";
89 //清除图
90 DeleGraph();
91 }
92 return 0;
93 }
实现思路
背景知识:基于DFS的边的分类
Forward Edges: 从DFS树的祖先到子孙。
Backward Edges: 从DFS树的子孙到祖先。
Cross Edges: DFS树上两个不是“祖先-子孙”关系的两个点之间的边。
存在Backward Edges <=> 图中有环