题目链接:http://poj.org/problem?id=1679
题目大意:
给你一个有权值的无相图,判断最小生成树是否唯一。
解题思路:
网上的资料很多,但是没有关于次小生成树的概念。从名字上看,次小生成树就是在最小生成树的基础上,其中某些边大于最小生成树,但是大于其他的生成树,类似于第二小生成树的意思。。。。
这道题就是用到了这种思想。
首先,我们可以通过prim算法得到这个图的最小生成树MST。之后,我们通过某些操作就可以判断MST是否唯一。
整体思想就是,先得到MST。然后,我们可以在MST外选择一条边加入MST,这时,必然会形成一个环,那么我们就在MST上删去一条边(不能是加入的边),这样,我们就得到了另一个MST。如果这个MST的权值之和和原来的MST权值相同,就有多种构图形成MST。输出YES。
算法实现流程:
1.首先使用prim算法为基础,在此基础上加入数组max1[i][j],用于记录i到j路径上的权值最大的边。
2.加入stack[i]数组,用于记录加入到MST中的顶点
3.加入pre[i]数组,用于记录加入MST的顶点的相关联边的直接前驱。
4.在找到temp和k后,需要进行一次循环,用于更新新加入点到MST各点路径最大值(作用在于,如果我要在这个顶点加边形成环,我需要去掉这个环上权值最大的边)至于为什么要用
max(max1[stack[j]][pre[k]], temp)
比较的是pre[k],因为加入的k点如果要跟MST其他点连接,必须通过它的直接前驱,而它的前驱一定是MST中已经确定的到各个顶点都是最大值的记忆化状态,所以我们要比较的是pre[k],而后面之所以是temp因为,temp是MST和非MST两个集合相连的最小权值的边,但是你要加入MST,不能保证他是MST中权值最小的(甚至是最大的)所以加入时需要用temp和max1[stack[j][pre[k]]比较,来得到max1[i][j](i到j路径上权值最大的边)
5.保存一下加入到MST中的顶点
6.更新lowxost,并且记录下更新后的直接前驱
7.prim算法结束后,max1数组存放的就是MST中各个顶点之间的权值最大的边。这时,我们需要对MST外的边加入到MST中并删除最大边的操作。这时候我们需要一个双层循环,遍历每一个MST外的边,当然,必须保证是MST外的边,那个判断语句就是防止是MST种的边,至于为什么i != pre[j] 和 j != pre[i]同时存在,是为了防止出现i=3,j=2和j=2,i=3,这种情况。这个判断一定要正确,要不然就会出现错误。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<algorithm>
using namespace std;
#define N 510
int map[N][N], lowcost[N], pre[N], max1[N][N], stack[N];
bool visit[N];
int n, m, sum;
void prim() //默认1在MST中
{
int temp, k;
int top; //保存最小生成树的结点
memset(visit, false, sizeof(visit)); //初始化
visit[1] = true;
sum = 0;
top = 0;
for(int i = 1; i <= n; ++i)
{
pre[i] = 1;
lowcost[i] = map[1][i];
}
lowcost[1] = 0;
stack[top++] = 1; //保存MST的结点
for(int i = 1; i <= n; ++i)
{
temp = INT_MAX;
for(int j = 1; j <= n; ++j)
if(!visit[j] && temp > lowcost[j])
temp = lowcost[k = j];
if(temp == INT_MAX)
break;
visit[k] = true;
sum += temp;
for(int j = 0; j < top; ++j) //新加入点到MST各点路径最大值
max1[stack[j]][k] = max1[k][stack[j]] = max(max1[stack[j]][pre[k]], temp);
stack[top++] = k; //保存MST的结点
for(int j = 1; j <= n; ++j) //更新
if(!visit[j] && lowcost[j] > map[k][j])
{
lowcost[j] = map[k][j];
pre[j] = k; //记录直接前驱
}
}
}
int main()
{
int ncase;
int start, end, cost;
int minn;
scanf("%d", &ncase);
while(ncase--)
{
for(int i = 1; i < N; ++i) //初始化不为0,1必须用循环。。。。
for(int j = 1; j < N; ++j)
{
map[i][j] = INT_MAX;
max1[i][j] = 0;
}
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i)
{
scanf("%d%d%d", &start, &end, &cost);
//if(cost < map[start][end])(POJ竟然出现重边的时候不选择最小的~~~)
map[start][end] = map[end][start] = cost;
}
prim();
minn = INT_MAX;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
if(i != j && i != pre[j] && j != pre[i]) //枚举MST以外的边
minn = min(minn, map[i][j] - max1[i][j]); //求出{MST外加入边-MST环上权值最大边}最小值
if(minn != 0)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}