hud 4081 次小生成树应用

这个题确实不简单,难就难在对题意的正确的理解上,
题意;

秦始皇想要修路使得n个城市连通,同时使得这些路尽量短。这时徐福说他可以在2个城市之间不花费人力物力建一条路,但他只能建1条这样的路。秦始皇想要使除了徐福造的那条路以外的路的总长度尽量短,而徐福想要造福更多的百姓,他想要使自己用法术建的那条路的两端的城市的总人口数最多。最终二人达成协议。假设A=用法术建的那条路的两端的城市的总人口数,B=除了徐福造的那条路以外的路的总长度,建造的路要使得A/B最大。输出A/B。

咱们先来分析一下这个题,一开始以为是直接在最小生成树上枚举边就行,再仔细一想,不对啊,最小生成树不唯一,这样枚举肯定不对啊,再去读题才明白,徐福建的这条路不一定是在最小生成树上,只要满足A / B 最大就可以,这样就可以分为两种情况
第一种情况是 如果这条边在最小生成树上的话,那直接就是 这条边相连的城市的 人口之和 / MST - 边权
第二种情况是 如果这条边不在最小生成树上的话,这种情况也是可以的,
原因是 只要 所连接的两个城市的人口如果特别大的话,A / B ,也满足题意。
那样的话,MST 在加入一条边就肯定成环,要使 A / B 最大,在A固定的情况下,B 最小就行啊,B = MST - 环中的边,现在转化成求这条最大的边。就是最小瓶颈路了,
问题就这样解决了,

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<iomanip>
using namespace std;
struct City
{
    int x,y,p;
};

const int MAXN = 1010;
const int MAX = 0x3f3f3f3f;
City city[MAXN];
double _map[MAXN][MAXN];
double maxcost[MAXN][MAXN];
bool tag[MAXN];
double dis[MAXN];
bool used[MAXN][MAXN];
int father[MAXN];
int n;
double f(City a,City b)
{
    double ans;
    double x = a.x - b.x;
    double y = a.y - b.y;
    ans = sqrt(x * x + y * y);
    return ans;
}
double prim()
{
    double ans = 0;
    memset(tag,0,sizeof(tag));
    memset(used,0,sizeof(used));
    memset(maxcost,0,sizeof(maxcost));
    for(int i = 1; i<= n;++i)
    {
        dis[i] = _map[1][i];
        father[i] = 1;
    }
    tag[1] = 1;
    father[1] = 0;// 遗漏

    for(int i = 1; i <= n - 1;++i)
    {
        int now = MAX;
        double _min = MAX;
        for(int j = 1; j <= n; ++j)
        {
            if(dis[j] < _min && !tag[j])
            {
                now = j;
                _min = dis[j];
            }
        }
        if(now == MAX)
            return -1;
        ans += _min;
        tag[now] = 1;
        used[father[now]][now] = used[now][father[now]] = 1; // 错误
        for(int j = 1;j <= n; ++j)
        {
            if(tag[j] && j != now)
                maxcost[now][j] = maxcost[j][now] = max(maxcost[father[now]][j],_min);
            if(!tag[j] && dis[j] > _map[now][j])
            {
                dis[j] = _map[now][j];
                father[j] = now;
            }
        }
    }
    return ans;
}
double smst()
{
    double ans = -1,mst;
    mst = prim();
    for(int i = 1;i <= n - 1;++i)
    {
        for(int j = i + 1;j <= n; ++j)
        {
            if(used[i][j])
                ans = max(ans,(city[i].p + city[j].p) * 1.0 / (mst - _map[i][j]));
            else
                ans = max(ans,(city[i].p + city[j].p) * 1.0 / (mst - maxcost[i][j]));
        }
    }
    return  ans;
}
int main()
{
    int T;
    ios::sync_with_stdio(false);
    cin >> T;
    while(T--)
    {
        cin >> n;
        for(int i = 1;i <= n; ++i)
        {
            cin >> city[i].x >> city[i].y >>city[i].p;
        }
        for(int i = 1;i <= n; ++i)
        {
            for(int j = i + 1; j <= n; ++j)
            {
                _map[i][j] =  _map[j][i] = f(city[i],city[j]);
            }
        }
        double ans = smst();
        cout << fixed << setprecision(2) << ans << endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值