次小生成树

最小生成树与次小生成树

我们都知道要求最小生成树有两种方法可求,分别是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,有明白的大神们,请留言啊。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值