题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=118
题目分析:
上次也做了那个最小生成树的题了,但是当时只是看懂了那两个算法,没有深入理解,然后这道题试图用上次的套路做,结果悲剧的TLE了,我贡献了N个WA啊,只能说明我太执着了。唉~不说了说多了都是泪。我纠结了一整天,一整天啊~~~
这道题其实上就是求次小生成树,判断次小生成树是不是和最小生成树一样大。加入一条不属于最小生成树的最小的边e,必然会有一个环,找出这个环中非e的长度最长的边,用e替换之即可。这里用一个二维数组就可以维护节点i到节点j的最长的边。当一个节点作为新节点v加入集合s的时候,s中已有的点与v的最长的边就是max(到其前驱节点的最长的边,前驱节点到v的边长)。
下面说说prim算法,其本质是把图分成两个集合,记以选入的最小生成树的集合为s,则与之对应的,其补集记为u,每一次迭代,其实就是选u中与集合s距离最近的点,加到s中,有了这个思想看代码也就不难了,我的代码都写上了注释,应该不难看懂。
#include<stdio.h>
#include<string.h>
const int N = 501;
int Map[N][N];//两点之间的距离
bool used[N];//节点是否用过
int dis[N];//集合外的点到集合s的最短距离
int pre[N];//集合外的点距离最近的集合s中的点
int MaxLen[N][N];//两个节点间的最大的边的长度
inline int max(const int a, const int b)
{
return a > b ? a : b;
}
void Prim(int n)
{
int i,j;
int Min,MinI,pr;
used[1] = true;
//初始状态,只有1在集合内,其他点到集合的距离即为到1的距离
for(i = 2; i <= n; ++i)
{
dis[i] = Map[1][i];
pre[i] = 1;
}
for(i = 1; i < n; ++i)
{
Min = 1e7;
//找出与集合s距离最近的点
for(j = 2; j <= n; ++j)
{
if(!used[j] && Min > dis[j])
{
MinI = j;
Min = dis[j];
}
}
pr = pre[MinI];
MaxLen[pr][MinI] = MaxLen[MinI][pr] = dis[MinI];
//更新集合s到新加入的点的最长边
for(j = 1; j <= n; ++j)
{
if(used[j])
MaxLen[j][MinI] = MaxLen[MinI][j] = max(MaxLen[j][pr], dis[MinI]);
}
used[MinI] = true;
//更新集合s到s外各点的距离
for(j = 2; j <= n; ++j)
{
if(!used[j] && Map[j][MinI] < dis[j])
{
dis[j] = Map[j][MinI];
pre[j] = MinI;
}
}
}
}
bool SecondMST(int n)
{
int i,j;
for(i = 1; i <= n; ++i)
{
for(j = i + 1; j <= n; ++j)
{
//说明节点i到j的边已经用过了
if(pre[i] == j || pre[j] == i)
continue;
else if(MaxLen[i][j] == Map[i][j])
return true;
}
}
return false;
}
int main()
{
int t,v,e;
int a,b,c;
int i,j;
scanf("%d", &t);
while(t--)
{
scanf("%d %d", &v, &e);
for(i = 1; i <= v; ++i)
for(j = 1; j <= v; ++j)
Map[i][j] = 1e7;
memset(used, 0, sizeof(used));
for(i = 0; i < e; ++i)
{
scanf("%d %d %d", &a, &b, &c);
Map[a][b] = Map[b][a] = c;
}
Prim(v);
if(SecondMST(v))
printf("Yes\n");
else
printf("No\n");
}
return 0;
}