SSL2672 2017年8月8日提高组T3 题目(二分+spfa)

32 篇文章 0 订阅
28 篇文章 0 订阅

2017年8月8日提高组T3 题目

Description

小C旅行到了美丽的x市。热爱oi的小C把x市分成了n个建筑物和m条双向街道,每一条街道都有一个通过时间,每个建筑物都有一个观赏值v。接着小C就在想了,如果我从任意一个点出发,经过至少两个点(包括出发点)后回到出发点,那么我能得到的最大平均观赏值是多少。平均观赏值指的是总观赏值/总耗费时间。你可以理解成观赏是不需要耗费时间的,且若多次到达同一建筑物,观赏值只会被计算一次。但小C还要和他的好朋友们开黑打农药,于是他就把这个又简单又无聊的问题交给了准备参加NOIP的你。

Input

第一行两个数分别表示n和m.
第二行n个整数,表示每个建筑物的观赏值v。
接下来m行,每行包含三个整数u,v,t,表示一条道路的两个端点和通过时间。

Output

输出一个实数,保留两位小数,表示最大平均观赏值。

分析:原题要求sigma(v)/sigma(t)最大,考虑二分答案,设当前答案为mid,若mid>=ans则必然有sigma(v)/sigma(t)<=mid,两边同乘分母得sigma(t)*mid-sigma(v)>=0.那么我们可以把每条边的边权变为t*mid-v[to],然后用spfa来判断是否存在负环即可。

代码

#include <cstdio>
#include <queue>
#include <cstring>
#define maxn 10000
using namespace std;

struct arr
{
    int y,w,nxt;
}a[maxn];
int l,n,m,b[maxn],ls[maxn],tot[maxn];
bool v[maxn],v1[maxn];
double d[maxn];

void add(int p,int q,int o)
{
    a[++l].y=q;
    a[l].w=o;
    a[l].nxt=ls[p];
    ls[p]=l;
}

bool spfa(int xx,double gg)
{
    queue<int> q;
    for (int i=0;i<=n;i++)
        d[i]=-1231456;
    memset(tot,0,sizeof(tot));
    memset(v,false,sizeof(v));
    v[xx]=true;
    d[xx]=0;
    q.push(xx); 
    while (!q.empty())
    {
        int t=q.front();
        q.pop();
        for (int i=ls[t];i;i=a[i].nxt)
        {
            double o=b[a[i].y]-gg*a[i].w;
            if (d[t]+o>d[a[i].y])
            {
                d[a[i].y]=d[t]+o;
                tot[a[i].y]++;
                if (tot[a[i].y]>n) return true;
                if (!v[a[i].y])
                {
                    v[a[i].y]=true;
                    v1[a[i].y]=true;
                    q.push(a[i].y);
                }
            }
        }
        v[t]=false;
    }
    return false;
}

bool check(double gg)
{
    memset(v1,false,sizeof(v1));
    for (int i=1;i<=n;i++)
        if (!v1[i])
        {
            v1[i]=true;
            if (spfa(i,gg)) return true;
        }
    return false;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&b[i]);
    for (int i=1;i<=m;i++)
    {
        int p,q,o;
        scanf("%d%d%d",&p,&q,&o);
        add(p,q,o);
    }
    double l=0.0,r=100.0,ans,mid;
    while (r-l>1e-5)
    {
        mid=(l+r)/2.0;
        if (check(mid)) 
            l=mid+1e-3;
        else r=mid-1e-3;
    }
    printf("%.2f",l);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值