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);
}