修路方案
时间限制:
3000 ms | 内存限制:
65535 KB
难度:
5
-
描述
-
南将军率领着许多部队,它们分别驻扎在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
-
第一行输入一个整数T(1<T<20),表示测试数据的组数
思路分析:考查次小生成树。之前没研究过这个问题,看了一些关于次小生成树的算法,各说纷纭......就挑了一个好理解的算法。感觉还是很麻烦,后续再整理一下吧。
首先,用kruskal求得最小生成树,并用visit数组记录最小生成树的边,假设为总共num条
然后,循环求最小生成树num次,每次都不用第一次求得的最小生成树的边
假设:第一次求最小生成树用到了 1、2、4这三条边,总共5条边,那循环3次的时候,每次分别不用1、2、4求得最小生成树的MST,最小的MST即为次小生成树
AC代码:不加剪枝险些超时,2600MS,加剪枝会大大优化,缩短到150MS
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#define MAX 500 + 10
#define INF 0x3fffffff
using namespace std;
typedef struct {
int a;
int b;
int l;
} Edge;
vector<Edge> edge;
int father[MAX];
int visit[MAX];
int MST;
int secMST;
int NumEdge;
void initSet() {
for( int i = 0; i < MAX; i++ ) {
father[i] = i;
}
}
int findFather( int x ) {
int a = x;
while( x != father[x] ) {
x = father[x];
}
while( a != father[a] ) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union( int a, int b ) {
int faA = findFather( a );
int faB = findFather( b );
father[faA] = faB;
}
int cmp( Edge a, Edge b ) {
return a.l < b.l ? 1 : 0;
}
void kruskal( int n ) {
initSet();
sort( edge.begin(), edge.end(), cmp );
int cnt = 0;
for( int i = 0; i < edge.size(); i++ ) {
int faA = findFather( edge[i].a );
int faB = findFather( edge[i].b );
if( faA != faB ) {
Union( edge[i].a, edge[i].b );
MST += edge[i].l;
visit[cnt] = i;
//printf( "选择边%d %d\n", edge[i].a, edge[i].b );
cnt++;
}
}
//if( cnt == n - 1 ) {
NumEdge = cnt;
//}
//printf( "%d %d\n", NumEdge, MST );
}
void secMinTree( int n ) {
for( int i = 0; i < NumEdge; i++ ) {
initSet();
int curMinTree = 0;
int cnt = 0;
for( int j = 0; j < edge.size(); j++ ) {
if( j != visit[i] ) {
int faA = findFather( edge[j].a );
int faB = findFather( edge[j].b );
if( faA != faB ) {
//printf( "选择边%d %d\n", edge[j].a, edge[j].b );
Union( edge[j].a, edge[j].b );
curMinTree += edge[j].l;
cnt++;
}
}
}
// 每次求得一个生成树,更新次小生成树的权值
if( cnt == n - 1 && curMinTree < secMST ) {
secMST = curMinTree;
}
// 剪枝,如果当前所求的次小生成树已经和最小生成树权值相同,return
if( secMST == MST ) return;
}
}
int main() {
int T;
scanf( "%d", &T );
while( T-- ) {
edge.clear();
MST = 0;
secMST = INF;
NumEdge = 0;
fill( visit, visit + MAX, 0 );
int n, m;
scanf( "%d%d", &n, &m );
int a, b, l;
Edge e;
for( int i = 0; i < m; i++ ) {
scanf( "%d%d%d", &a, &b, &l );
e.a = a;
e.b = b;
e.l = l;
edge.push_back( e );
}
kruskal( n ); // 求最小生成树
secMinTree( n ); // 求次小生成树
//printf( "%d %d\n", MST, secMST );
if( MST == secMST ) {
printf( "Yes\n" );
}
else {
printf( "No\n" );
}
}
return 0;
}