题目链接:http://bailian.openjudge.cn/practice/1679?lang=en_US
题意:
给出点与点之间的权值,问是否能构成唯一的最小生成树,能则输出最小值,否则输出 Not Unique!
思路:
次小生成树数就是第二小的生成树,仅比最小生成树大;求出次小生成树,判断是否和最小生成树相等,是则不唯一;
这里求次小生成树的方法有两个,一个是基本算法:依次枚举最小生成树的边并去掉,再求最小生成树,这些新求出的最小生成树的值中最小的那个就是次小生成树的值;
用kruskal先求出最小生成树,用used记录最小生成树的边,在不考虑used内任意一个边的情况下做kruskal,得到的 n 个最小生成树 (n的值可能不等于used存放的边数,因为可能存在无法构成最小生成树的情况),那么这n个最小生成树的值中最小的那个,就是次小生成树的权值和;时间复杂度是 V*ElogE(适合稀疏图);
第二个:求出最小生成树中,任意两点之间的最大权值边,取出这些最大权值边中最小的那个值,那么次小生成树 = 最小生成树 的权值和+ 两点直接连接的权值 - 最小的两点之间的权值边(不考虑两点之间的权值);通俗的讲就是在最小生成树中再加一条边,那么这个树必然有环,在这个环中找除新加入的边以外的那条最大的权值边,如果用新边代替这条最大的权值边,那么得到的生成树一定比最小生成树的权值和大,在这些比最小生成树大的权值和中找最小的那个就是次小生成树的权值和;
在用prim求最小生成树的时候,用Max记录两点内的最大权值边,在求出最小生成树后,Max对应的就是两点边之间间接连接的最大权值边的值,用上面的公式就能求出次小生成树的值;时间复杂度 2*n*n (适合稠密图)
这里提示一下Max的求法(结合下面第二个代码),有点像DP;
这里的prim是《挑战程序设计》中的模板改过来的,代码的模板:把最小生成树的点放在一个集合X中,每加入一个点,就把不在这个集合X的所有点到这个新加入的点的距离做一次比较(距离cost初始化为 INF),如果比本身的cost大,那么就更新当前这个cost,并且用一个数组to来记录这个点指向的边,也就是 to[ i ] = j i到j的权值为cost[ i ] ,j为最小生成树内的点;而cost的实际意义就是当前点到最小生成树中的点中权值最小的值;下次再添加新点的时候就是找cost最小的那个;
最起初的时候,最小生成树只有一个点,那么Max[ i ][ i ] = 0 ( 自己到自己的最大权值肯定是0),当再加入一个点的时候,Max[ i ][ j ] 就是两点之间的权值;再加入一个点k,如果这个k的to是j,那么Max[ k ][ i ] = max(cost[ k ][ j ] ,Max[ j ][ i ]);每加入一个点的时候,就把新添加的点到集合X内所有的点的Max求出来,这样不管下次新加入的点到集合内其他的点有没有边,都能通过比较cost[ 当前点 ][ to ] 和 Max[ to ][ 集合X内任意点 ]的大小来求出Max[ 当前点 ][ 集合X内任意点 ];并不用担心我们需要知道的Max还没求出来,自己可以多在草稿纸上模拟几次,多添加几次点就知道,我们需要知道的Max总是提前求出来的了;感觉有点像DP的数据记忆;
kruskal 实现的基本算法
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int Maxn = 150;
const int INF = 0x3f3f3f3f;
struct Edge {
int x,y,w;
};
int pre[Maxn],Rank[Maxn],used[Maxn*Maxn],V,M,res,ans,min_res;
bool ok;
Edge edge[Maxn*Maxn];
bool cmp (Edge &a1, Edge &a2) {
if(a1.w < a2.w) return true;
else return false;
}
void make (int x) {
pre[x] = x;
Rank[x] = 0;
}
int Find (int x) {
if(pre[x] != x) pre[x] = Find (pre[x]);
return pre[x];
}
void merge_ (int x,int y) {
int xx = Find (x);
int yy = Find (y);
if(yy == xx) return;
if(Rank[yy] > Rank[xx]) pre[xx] = yy;
else {
pre[yy] = xx;
if(Rank[xx] == Rank[yy]) Rank[xx]++;
}
}
void kruskal (int x) {
for (int i = 1; i <= V; ++i) make (i);
int cnt = 0; res = 0;
for (int i = 1; i <= M; ++i) {
if(x && i == x) continue; // 遇到 x 不是0的时候,表示x的值代表的是删除的边,做kruskal时不考虑就行
if(Find (edge[i].x) != Find (edge[i].y)) {
merge_ (edge[i].x,edge[i].y);
res+=edge[i].w;
cnt++;
if(!x) used[i] = 1; // x = 0的时候是求最小生成树的时候,记录对应的边
}
}
if(!x) ans = res;
if(x && cnt+1 == V && ans == res) ok = false;
}
int main(void)
{
int t;
scanf("%d",&t);
while (t--) {
scanf("%d%d",&V,&M);
for (int i = 1; i <= M; ++i) {
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].w);
}
memset(used,0,sizeof(used));
sort(edge+1,edge+M+1,cmp);
ok = true;
kruskal(0); //带入参数 0 ,只求最小生成树
for (int i = 1; i <= M; ++i) {
if(used[i]) { // 带入的 i 是最小生成树边
kruskal(i);
if(!ok) break;
}
}
if(ok) printf("%d\n",ans);
else printf("Not Unique!\n");
}
return 0;
}
prim算法
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 150;
const int INF = 0x3f3f3f3f;
int V,G[maxn][maxn],vis[maxn],used[maxn][maxn],Max[maxn][maxn],cost[maxn],to[maxn];
void init () {
for (int i = 1; i <= V; ++i) {
for (int j = 1; j <= V; ++j) {
if(i == j) G[i][j] = 0;
else G[i][j] = INF;
}
}
}
void solve (int min_mst);
void prim () {
memset(Max,0,sizeof(Max)); // 记录两点之间的最大权值
memset(used,0,sizeof(used)); // 用来记录最小生成树的边
memset(to,0,sizeof(to)); // to[i] 代表的是 i 对应的另一个点
for (int i = 1; i <= V; ++i) {
cost[i] = INF; // cost[i] 是点i到已经访问过的点中的最小路径
vis[i] = 0;
}
int mst = 0,v,cnt = 0; cost[1] = 0;
while (1) {
v = -1;
for (int u = 1; u <= V; ++u) {
if(!vis[u] && (v == -1 || cost[u] < cost[v])) v = u;
}
if(v == -1) break; vis[v] = 1; mst+=cost[v];
if(to[v]) {
used[v][to[v]] = used[to[v]][v] = 1;
cnt++;
}
for (int u = 1; u <= V; ++u) {
if(vis[u]) Max[u][v] = Max[v][u] = max(cost[v],Max[to[v]][u]); // 更新cost的时候
if(!vis[u] && G[u][v] != INF) { //遇到已经访问过的点就更新Max的值
if(cost[u] > G[u][v]) {
cost[u] = G[u][v];
to[u] = v;
}
}
}
}
if(cnt+1 != V) printf("Not Unique!\n");
else solve (mst);
}
void solve (int min_mst) {
int ans = INF;
for (int u = 1; u < V; ++u) {
for (int v = u+1; v <= V; ++v) {
if(!used[u][v] && G[u][v] != INF)
ans = min(ans,min_mst+G[u][v]-Max[u][v]); // 枚举不属于最小生成树的边
}
}
if(ans == min_mst) printf("Not Unique!\n");
else printf("%d\n",min_mst);
}
int main(void)
{
int t,u,v,w,edge;
scanf("%d",&t);
while (t--) {
scanf("%d%d",&V,&edge);
init (); // 邻接矩阵的初始化,尽量规范化,没有边的情况下为 INF,i = j的时候为0
for (int i = 1; i <= edge; ++i) {
scanf("%d%d%d",&u,&v,&w);
G[u][v] = w;
G[v][u] = w;
}
prim ();
}
return 0;
}