Gym 100206B Fire station building(三分)

题目地址:http://codeforces.com/gym/100206/attachments

思路:

1.求出任意两点最短距离g(i,j),对于建在边u-v长为len的消防站(位置为x),则到点i的最短距离为min{g[u][i]+x,len-x+g[v][i]}(r<=x<=l-r)。

2.枚举每条可建消防站的边,显然总期望距离为一凹函数,三分求取最小值即可。

3.当图不连通或道路长度不足以建站时无解,当n=1且r=0时期望为0.

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define debug
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-6;
const int maxn=100+50;

struct Edge
{
    int u,v,l;
};

int n,m,r;
int g[maxn][maxn];
Edge e[maxn*maxn];
int fa[maxn];
double a[maxn];

void init()
{
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            g[i][j]=INF;
        }
    }

    for(int i=1; i<=n; i++) fa[i]=i,g[i][i]=0;
}

int Find(int x)
{
    return fa[x]==x?x:fa[x]=Find(fa[x]);
}

void floyd()
{
    for(int k=1; k<=n; k++)
    {
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
            }
        }
    }
}

double check(double pos,int u,int v,int len)
{
    double sum=0.0;
    for(int i=1; i<=n; i++)
    {
        sum+=a[i]*min(g[u][i]+pos,g[v][i]+len-pos);
    }
    return sum;
}

double solve(double l,double r,int u,int v,int len)
{
    while(fabs(r-l)>eps)
    {
        double mid1=(l+r)/2.0;
        double mid2=(mid1+r)/2.0;
        if(check(mid1,u,v,len)<check(mid2,u,v,len))
        {
            r=mid1;
        }
        else
        {
            l=mid2;
        }
    }
    return l;
}

int main()
{
#ifdef debu
    freopen("in.txt","r",stdin);
#endif // debug

    freopen("fire.in","r",stdin);
    freopen("fire.out","w",stdout);

    scanf("%d%d%d",&n,&m,&r);

    init();

    for(int i=1; i<=n; i++)
    {
        scanf("%lf",&a[i]);
    }
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].l);
        g[e[i].u][e[i].v]=g[e[i].v][e[i].u]=e[i].l;

        int u=Find(e[i].u),v=Find(e[i].v);
        if(u!=v) fa[u]=v;
    }

    int tmp=Find(1),flag=0;
    for(int i=2; i<=n; i++)
    {
        if(tmp!=Find(i))
        {
            flag=1;
            break;
        }
    }

    if(flag)
    {
        printf("-1\n");
    }
    else
    {
        floyd();

        double ans=1e18;
        for(int i=1; i<=m; i++)
        {
            if(e[i].l>=2*r)
            {
                int l=e[i].l;
                int u=e[i].u,v=e[i].v;
                double pos=solve(r,e[i].l-r,u,v,l);
                ans=min(ans,check(pos,u,v,l));
            }
        }
        if(n==1&&r==0) printf("0\n");
        else if(ans>=1e18) printf("-1\n");
        else printf("%.5f\n",ans/10000);
    }

    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值