acm专题学习之生成树(二)次小生成树+HDU-4081

题目:秦始皇想要修路,他只想着修总长度最小(最小生成树)。但有个道士告诉他,可以用魔法修一条路,不耗费人力财力的一条魔法路。秦始皇想要使非魔法道路总长度尽可能小,道士希望尽可能多的人受益。最终决定:修魔法路的两个城市,(两个城市的总人口)/(非魔法道路总长度)必须是最大。

输入:t个测试数据,n个城市,每个城市输入为坐标(x,y)和人口p

输出:(两个城市的总人口)/(非魔法道路总长度)最大值

次小生成树:在最小生成树的基础上,枚举每一条不在最小生成树上的边,把边放到最小生成树上,然后就会形成环路,去掉环路上除了新加入的边的最长边,根据附加的条件得到要的答案。

具体实现:除了基本的最小生成树,还有加入connect数组用来记录不属于最小生成树的连通边(方便枚举每一条不在最小生成树上的边),maxd数组记录最小生成树中i点到j点中最大的距离(方便后面删去环路上除了新加入的边的最长边),per数组记录最小生成树里面每个点的上一个链接的点(方便得到父坐标)

代码:

#include <algorithm>
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1005;
const int inf=1e9+9;
int n;
bool vis[maxn];//记录最小生成树集合
double dis[maxn];//更新最小生成树集合到其他点的距离
double maxd[maxn][maxn];//maxd[i][j]表示最小生成树里i点到j点中最大的距离
int per[maxn];//最小生成树里面每个点的上一个链接的点
double connect[maxn][maxn];//记录是否是通路,且该通路不在最小生成树里面
struct nod
{
    double x,y;
    double p;
} num[maxn];
double calculate(int i,int j)//计算两点的距离
{
    return sqrt((num[i].x-num[j].x)*(num[i].x-num[j].x)+(num[i].y-num[j].y)*(num[i].y-num[j].y));
}
double Prim()
{
    memset(maxd,0,sizeof(maxd));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)//选第一个点
    {
        dis[i]=calculate(1,i);
        per[i]=1;
    }
    vis[1]=1;
    double sum=0;
    for(int i=1;i<n;i++)
    {
        double min1=inf;
        int p=-1;
        for(int j=1;j<=n;j++)//找出与最小生成树当前集合相连的最小距离的点
        {
            if(!vis[j]&&dis[j]<min1)
            {
                p=j;
                min1=dis[j];
            }
        }
        if(p==-1)//如果找不到了,就说明已经找完了
            return sum;
        vis[p]=1;
        //把这条边从通路中删除,保留非生成树通路
        connect[p][per[p]]=false;
        connect[per[p]][p]=false;
        sum+=min1;
        maxd[per[p]][p]=maxd[p][per[p]]=min1;
        for(int j=1;j<=n;j++)
        {
            if(j!=p&&vis[j])
            {
                maxd[p][j]=maxd[j][p]=max(maxd[j][per[p]],dis[p]);
            }
            if(!vis[j]&&calculate(j,p)<dis[j])
            {
                dis[j]=calculate(j,p);
                per[j]=p;
            }
        }
    }
    return sum;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        //memset(mp,inf,sizeof(mp));
        memset(connect,true,sizeof(connect));
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
        {
            scanf("%lf%lf%lf",&num[i].x,&num[i].y,&num[i].p);
        }
        double sum=Prim();
        double ans=-1;
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                if(connect[i][j])
                {
                    ans=max(ans,1.0*(num[i].p+num[j].p)/(sum-maxd[i][j]));
                }
                else
                {
                    ans=max(ans,1.0*(num[i].p+num[j].p)/(sum-calculate(i,j)));
                }
            }
        }
        printf("%.2lf\n",ans);
    }
    return 0;
}

总结:1 学到了新东西 2 看英文题的时候,不要太依赖网页翻译工具,还是要自己看看英文。(这次网页翻译出错,导致一直看错题)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值