最小生成树与次小生成树
我们都知道要求最小生成树有两种方法可求,分别是prim算法和kruskal算法
1.prim算法
设图G =(V,E),其生成树的顶点集合为U。
①、把v0放入U。
②、在所有u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树。
③、把②找到的边的v加入U集合。如果U集合已有n个元素,则结束,否则继续执行②。
2.kruskal算法首先将所有边按边权排序,然后按照边权从小到大依次处理.这里要用到并查集的思想,假设已有点集U,现在正在处理边i->j,如果i,j已在
U中则处理下一条边,否则将i,j加入并查集中.继续处理下一条边,直到有n-1条边为止.
3 次小生成树
次小生成树可由最小生成树换一条边得到
算法:
1)先用prim求出最小生成树T,在prim的同时,用一个矩阵max[u][v]记录在树中连接u-v的路径中权值最大的边.
2)枚举所有不在T中的边u-v,加入边u-v,删除权值为max[u][v]的边,不断枚举找到次小生成树.
下面给出次小生成树的模板
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
int g[110][110],dist[110],mmax[110][110]; ///g保存地图 dist保存从起点到其余各点的距离 maxn保存从i到j的最大边权值
int pre[110]; ///pre保存j的离它最近的是哪个点
bool mark[110]; ///相当于vis 用来标记该点是否已经用过
bool connect[110][110]; ///保存i-j的那条边是否加入了最小生成树 false 加入 true 没有
int mst,mint; ///mst保存最小生成树的权值和
int n,m;
int prim()
{
int res=0,fa,p,min,i,j;
memset(mmax,0,sizeof(mmax));
for(i=1;i<=n;i++)
{
dist[i]=g[1][i];
pre[i]=1;
mark[i]=false;
}
dist[1]=0;
mark[1]=true;
for(i=1;i<n;i++)
{
p=-1;min=INF;
for(j=1;j<=n;j++)
{
if(!mark[j]&&dist[j]<min)
{
p=j;
min=dist[j];
}
}
if(p==-1) return res;
mark[p]=true;
res+=dist[p];
fa=pre[p]; ///找到离p最近的点
connect[fa][p]=false;
connect[p][fa]=false;
mmax[fa][p]=min;
///遍历所有的点 求其余点到p的最大权值
for(j=1;j<=n;j++)
mmax[j][p]=(mmax[fa][p]>mmax[j][fa])?mmax[fa][p]:mmax[j][fa];
for(j=1;j<=n;j++)
{
if(!mark[j]&&dist[j]>g[p][j])
{
dist[j]=g[p][j];
pre[j]=p;
}
}
}
return res;
}
int main()
{
int tc;
scanf("%d",&tc);
while(tc--)
{
scanf("%d %d",&n,&m);
memset(g,INF,sizeof(g));
memset(connect,false,sizeof(connect));
while(m--)
{
int u,v,c;
scanf("%d %d %d",&u,&v,&c);
g[u][v]=c;
g[v][u]=c;
connect[u][v]=true;
connect[v][u]=true;
}
mst=prim();
int i,j;
bool flag=false;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
///如果i-j这条边加入了最小生成树 或者i-j这条路不通 continue
if(connect[i][j]==false||g[i][j]==INF)
continue;
///如果加入的边和删除的边的大小是一样的 说明次小生成树的权值和等于最小生成树的权值和
///也就是说最小生成树不唯一
if(g[i][j]==mmax[i][j])
{
flag=true;
break;
}
}
if(flag)
printf("Not Unique!\n");
else
printf("%d\n",mst);
}
return 0;
}
下面来一道题练练手
http://acm.hdu.edu.cn/showproblem.php?pid=4081
题意:
题目大意:
有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点。秦始皇希望这所有n-1条路长度之和最短。然后徐福突然有冒出来,说是他有魔法,可以不用人力、财力就变出其中任意一条路出来。
秦始皇希望徐福能把要修的n-1条路中最长的那条变出来,但是徐福希望能把要求的人力数量最多的那条变出来。对于每条路所需要的人力,是指这条路连接的两个城市的人数之和。
最终,秦始皇给出了一个公式,A/B,A是指要徐福用魔法变出的那条路所需人力, B是指除了徐福变出来的那条之外的所有n-2条路径长度之和,选使得A/B值最大的那条。
分析与总结
为了使的A/B值最大,首先是需要是B尽量要小,所以可先求出n个城市的最小生成树。然后,就是决定要选择那一条用徐福的魔法来变。
因此,可以枚举每一条边,假设最小生成树的值是MinMST, 而枚举的那条边长度是w[i][j], 如果这一条边已经是属于最小生成树上的,那么最终式子的值是A/(MinMST-w[i][j])。如果这一条不属于最小生成树上的, 那么添加上这条边,就会有n条边,那么就会使得有了一个环,为了使得它还是一个生成树,就要删掉环上的一棵树。 为了让生成树尽量少,那么就要删掉除了加入的那条边以外,权值最大的那条路径。 假设删除的那个边的权值是path[i][j], 那么就是A/(MinMST-path[i][j]).
解这题的关键也在于怎样求出次小生成树。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define INF 0x3f3f3f3f
#define MAX 1010
using namespace std;
double Map[MAX][MAX], dis[MAX], maxn[MAX][MAX]; ///Map数组保存地图 dis保存从起点到其他点的距离 maxn保存i到j的最大权值
int pre[MAX], vis[MAX];
bool used[MAX][MAX];
int n;
struct Edge
{
double x, y, p;
}edge[MAX];
double cal(Edge a, Edge b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
double prim()
{
double ret = 0, minn;
int father;
memset(vis, 0, sizeof(vis));
memset(maxn, 0, sizeof(maxn));
memset(used, 0, sizeof(used));
for(int i = 1; i <= n; i++)
{
dis[i] = Map[1][i];
pre[i] = 1;
}
vis[1] = 1;
int k;
for(int i = 1; i < n; i++)
{
minn = INF;
for(int j = 1; j <= n; j++)
{
if(!vis[j]&&dis[j] < minn)
{
minn = dis[j];
k = j;
}
}
if(minn == INF) return -1;
vis[k] = 1;
ret+=minn;
father = pre[k];
used[father][k] = used[k][father] = 1;
maxn[father][k] = minn;
for(int j = 1; j <= n; j++)
{
if(vis[j]&&j!=k) ///不明白为什么j为什么不能等于k
maxn[j][k] = maxn[k][j] = max(maxn[j][father], maxn[father][k]);
}
for(int j = 1; j <= n; j++)
{
if(!vis[j]&&dis[j] > Map[k][j])
{
dis[j] = Map[k][j];
pre[j] = k;
}
}
}
return ret;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
Map[i][j] = INF;
}
Map[i][i] = 0;
}
for(int i = 1; i <= n; i++)
{
scanf("%lf%lf%lf",&edge[i].x, &edge[i].y, &edge[i].p);
}
for(int i = 1; i <= n; i++)
{
for(int j = i + 1; j <= n; j++)
{
Map[i][j] = Map[j][i] = cal(edge[i], edge[j]);
}
}
double ans = prim();
double ans1 = -1;
for(int i = 1; i<= n; i++)
{
for(int j = 1; j <= n; j++)
{
if(i != j)
{
if(used[i][j])
ans1 = max(ans1, (edge[i].p + edge[j].p)/(ans - Map[i][j]));
else
ans1 = max(ans1, (edge[i].p + edge[j].p)/(ans - maxn[i][j]));
}
}
}
printf("%.2lf\n",ans1);
}
return 0;
}
但是一直有一个疑问,就是不明白为什么遍历各个点到k的最大边权值的时候,j不能等于k,有明白的大神们,请留言啊。