一直以为次小生成树的权值之和应该比最小生成树的权值和要大,今天看了次小生成树的得到过程,原来如果一个图中存在多个最小生成树,那么次小生成树的权值和和最小生成树的权值和是一样大的。
结论:次小生成树可由最小生成树换一条边得到。
证明:T是某一棵最小生成树,T0是一棵异于T的树,通过变换T0-->T1-->T2-->...-->Tn(T)变成最小生成树,所谓的变换, 是每次把Ti中的某条边换成T中的一条边,而且Ti+1的权小于等于Ti的权。
具体操作是:
step1.在Ti中任取一条不在T中的边u_v.
ster 2.把边u_v去掉,就剩下两个连通分量A和B,在T中,必有唯一的边u`_v`连接A和B.
step3.显然u`_v`的权比u_v小(否则u_v就应该在T中),把u`_v`替换u_v既得到树Ti+1;
特别的:取Tn为任一棵最小生成树,Tn-1也就是次小生成树且跟T差一条边,结论得证。
算法:只要充分利用以上结论,既得V^2的算法。具体如下:
step 1. 先用prim算法求出最小生成树T, 在prim的同时,用一个矩阵max[u][v]记录在T中连接任意两点u、v的唯一的路中权值最大的那条边的权值。具体实现:在prim中每次增加一个结点v, 记录得到结点v的父亲结点father[v],设已经加入最小生成树中的结点集合为w,则w中所有的结点到v中的最大权值为
max(新加入的边的权值,父亲结点到集合w中任意结点的权值)
step 2.枚举所有不在T中的边u_v,加入边u_v替换权为max[u][v]的边。不断更新求最小值(u_v与max[u][v]的差值),既得次小生成树。
实战:
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=118
修路方案
-
描述
-
南将军率领着许多部队,它们分别驻扎在N个不同的城市里,这些城市分别编号1~N,由于交通不太便利,南将军准备修路。
现在已经知道哪些城市之间可以修路,如果修路,花费是多少。
现在,军师小工已经找到了一种修路的方案,能够使各个城市都联通起来,而且花费最少。
但是,南将军说,这个修路方案所拼成的图案很不吉利,想让小工计算一下是否存在另外一种方案花费和刚才的方案一样,现在你来帮小工写一个程序算一下吧。
-
输入
-
第一行输入一个整数T(1<T<20),表示测试数据的组数
每组测试数据的第一行是两个整数V,E,(3<V<500,10<E<200000)分别表示城市的个数和城市之间路的条数。数据保证所有的城市都有路相连。
随后的E行,每行有三个数字A B L,表示A号城市与B号城市之间修路花费为L。
输出
- 对于每组测试数据输出Yes或No(如果存在两种以上的最小花费方案则输出Yes,如果最小花费的方案只有一种,则输出No) 样例输入
-
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
样例输出
-
No Yes
题目分析:先求出最小生成树的权值和,再求出次小生成树的权值和,如果两个权值和相等,则说明图中至少有两个最小生成树,否则只有一个最小生成树。
#include <cstdio> #include <cstring> #define INF 0x3f3f3f3f #define V 505 int max(int a, int b) { return a > b ? a : b; } int vis[V], lowc[V], father[V]; //fahter[i]记录当第i个点加入时的父亲结点 int cost[V][V], n; int maxd[V][V]; //最小生成树中任意两点之间的路径上的权值最大的边的权值 int que[V], len; int prim() { int i, j, p; int minc, res = 0; len = 0; memset(vis, 0, sizeof(vis)); vis[0] = 1; que[len++] = 0; for(i=1; i<n; i++) { lowc[i] = cost[0][i]; father[i] = 0; } for(i=1; i<n; i++) { minc = INF; p = -1; for(j=0; j<n; j++){ if(vis[j] == 0 && minc > lowc[j]) { minc = lowc[j]; p = j; } } if(INF == minc) return -1; res += minc; vis[p] = 1; que[len++] = p; //记录已经加入到最小生成树中的点 //当有新结点加入到最小生成树中后,更新maxd的值,记录已经存在的任意点到新加入的点之间 //路径上权值最大的边的权值 for(j=0; j<len-1; j++) { maxd[que[j]][p] = maxd[p][que[j]] = max(maxd[father[p]][que[j]], minc); //maxd的值为新加入的边和父亲结点的maxd的最大值 } for(j=0; j<n; j++) { if(vis[j] == 0 && lowc[j] > cost[p][j]) { lowc[j] = cost[p][j]; father[j] = p; //最小生成树上的结点的父亲结点为距离当前结点最近的结点 } } } return res; } int main() { int v, e, a, b, l; int t; scanf("%d", &t); while(t--) { scanf("%d%d", &v, &e); n = v; for(int i=0; i<v; i++) for(int j=0; j<v; j++) { maxd[i][j] = -1; if(i == j) cost[i][j] = 0; else { cost[i][j] = INF; cost[j][i] = INF; } } for(int i=0; i<e; i++) { scanf("%d%d%d", &a, &b, &l); if(l < cost[a-1][b-1]) { cost[a-1][b-1] = l; cost[b-1][a-1] = l; } } int ans; ans = prim(); int flag = 0; for(int i=0; i< n; i++) { for(int j=0; j<n; j++) { if(i == j) continue; //i,j之间的边cost[i][j]与i,j之间在最小生成树中路径上权值最大的边的权值比较 //i,j在最小生成树中不能相邻(father[i]!=j && father[j]!=i),否则maxd[i][j]和 //cost[i][j]表示的是同一条边 if(maxd[i][j] == cost[i][j] && father[i]!=j && father[j]!=i) { flag = 1; break; } } if(flag) break; } if(flag) printf("Yes\n"); else printf("No\n"); } return 0; }
-
第一行输入一个整数T(1<T<20),表示测试数据的组数