HDU 4081 Qin Shi Huang's National Road System(次小生成树-Kruskal)

Description
n个城市,每个城市有一个坐标(x[i],y[i])和一个居民数p[i],秦始皇想用n-1条道路把这n个城市连接起来,两个城市用直线段连接,要求任意两个城市互通且这n-1条道路的长度和最小,一个道士徐福说他可以把要修的n-1条道路中最长的那条道路给变出来,但秦始皇想让徐福把道路所连两个城市的居民数之和最大的那条道路变出来,所以秦始皇定义了一个值A/B,A是徐福变出的道路所连两个城市的居民数之和,B是剩下n-2条道路的长度之和,问A/B的最大值
Input
第一行一整数T表示用例组数,每组用例输入一个整数n表示城市数,之后输入n个城市的坐标和居民数x[i],y[i],p[i]
(T<=10,2 < n<=1000,0<=x[i],y[i]<=1000,0 < p[i] < 100000)
Output
输出A/B的最大值,结果保留到小数点后两位
Sample Input
2
4
1 1 20
1 2 30
200 2 80
200 1 100
3
1 1 20
1 2 30
2 2 40
Sample Output
65.00
70.00
Solution
对于一个固定的边u->v,A值固定,为使A/B最大需使B尽量小,所以求的是过u->v边的MST,先对原图求一遍MST,如果u->v这条边是树边则过这条边的MST就是不加限制的MST,如果不是,则从MST中u->v的路径中选取一条最长边删掉然后加入u->v这条边形成一个生成树即为过u->v这条边的MST,在Kruscal求MST的过程中维护len[u][v](MST上u到v路径上边权最大值),每次新加一条边x->y时,x所在连通块中的点u到y所在连通块中的点v的路径上边权最大值即为x->y这条边的边权(因为是把边按边权从小到大排序后再从小到大一条条插入的),求出MST后,设MST的权值和为mst,设答案为ans,对于权值为c的树边u->v有ans=min(ans,mst-c),对于非树边u->v有ans=min(ans,mst-len[u][v])
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define maxn 1111
#define maxm 1111111
struct node1
{
    int u,v,flag;
    double w;
}Edge[maxm];
struct node2
{
    int to,next;
}Link[maxn];
int T,E,V,Fa[maxn],Head[maxn],End[maxn];
double len[maxn][maxn];
int cmp(node1 a,node1 b)
{
    if(a.w!=b.w)return a.w<b.w;
    if(a.u!=b.u)return a.u<b.u;
    return a.v<b.v;
}
int find(int x)
{
    if(Fa[x]==x) return x;
    return Fa[x]=find(Fa[x]);
}
void unite(int x,int y)
{
    x=find(x),y=find(y);
    if(x==y) return ;
    Fa[x]=y;
    Link[End[y]].next=Head[x];
    End[y]=End[x];
}
double kruskal()
{
    int cnt=0;
    double mst=0;
    for(int i=1;i<=V;i++)
        Link[i].to=i,Link[i].next=-1,Head[i]=i,End[i]=i;
    sort(Edge,Edge+E,cmp);
    for(int i=1;i<=V;i++)Fa[i]=i;
    for(int k=0;k<E;k++)
    {
        int x=find(Edge[k].u),y=find(Edge[k].v);
        if(x!=y)
        {
            for(int i=Head[x];~i;i=Link[i].next)
                for(int j=Head[y];~j;j=Link[j].next)
                {   
                    int u=Link[i].to,v=Link[j].to;
                    len[u][v]=len[v][u]=Edge[k].w;
                }
            unite(x,y);
            Edge[k].flag=1;
            mst+=Edge[k].w; 
            cnt++;
            if(cnt==V-1)return mst;
        }
    }
}
int x[maxn],y[maxn],p[maxn];
double get(int x1,int y1,int x2,int y2)
{
    double x=x2-x1,y=y2-y1;
    return sqrt(x*x+y*y);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&V);
        for(int i=1;i<=V;i++)scanf("%d%d%d",&x[i],&y[i],&p[i]);
        E=0;
        for(int i=1;i<=V;i++)
            for(int j=i+1;j<=V;j++)
                Edge[E].u=i,Edge[E].v=j,Edge[E].w=get(x[i],y[i],x[j],y[j]),Edge[E++].flag=0;
        double mst=kruskal(),ans=0;
        for(int i=0;i<E;i++)
        {
            int u=Edge[i].u,v=Edge[i].v,flag=Edge[i].flag;
            double w=Edge[i].w;
            if(flag)ans=max(ans,1.0*(p[u]+p[v])/(mst-w));
            else ans=max(ans,1.0*(p[u]+p[v])/(mst-len[u][v]));
        }
        printf("%.2f\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值