POJ传送门
找一个环,最大化 ∑ v a l i ∑ c o s t i \frac{\sum val_i}{\sum cost_i} ∑costi∑vali
其中 v a l i val_i vali为点权, c o s t i cost_i costi为边权
令 r = ∑ v a l i ∑ c o s t i r=\frac{\sum val_i}{\sum cost_i} r=∑costi∑vali
有 r ∗ ∑ c o s t i − ∑ v a l i = 0 r*\sum cost_i-\sum val_i=0 r∗∑costi−∑vali=0
设函数 f ( r ) = r ∗ ∑ c o s t i − ∑ v a l i f(r)=r*\sum cost_i-\sum val_i f(r)=r∗∑costi−∑vali
显然对于不同的环对应的直线也不同,但是 r r r始终是正数
如图所示,不同的环对应不同的直线,二那些与 x x x轴的交点就是我们的合法值
所以二分一个 m i d mid mid,作直线 x = m i d x=mid x=mid交直线若干点
若存在直线交点是负数,说明最大值还在右边
若所有交点都是正数,说明最大值在左边
现在任务变成了判断是否所有的交点都是正数,所以求最小值即可
不过这个问题比较特殊,我们选的集合是有限定的,必须是一个环
所以想象一下把每个点拆点成两个,彼此连一条 − v a l i -val_i −vali的边,图中原来的边变成 r ∗ c o s t i r*cost_i r∗costi
所以直接跑最短路判断有没有负环即可,更方便了
#include <iostream>
#include <queue>
#include <stdio.h>
using namespace std;
const double eps=1e-7;
const int maxn = 2e5+10;
double p[maxn],dis[maxn];
int num[maxn],n,m,vis[maxn];
struct edge{
int to,nxt; double w;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,double w)
{
d[++cnt] = (edge){v,head[u],w},head[u]=cnt;
}
bool spfa(double mid)
{
queue<int>q;
for(int i=0;i<=n;i++) dis[i]=1e9,vis[i]=num[i]=0;
q.push( 1 ); num[1]++; dis[1]=0;
while( !q.empty() )
{
int u = q.front(); q.pop();
vis[u]=0;
for(int i=head[u];i;i=d[i].nxt )
{
int v=d[i].to;
double val = mid*d[i].w-p[v];
if( dis[u]+val+eps<dis[v] )
{
dis[v] = dis[u]+val;
if( !vis[v] )
{
vis[v]=1; num[v]++; q.push(v);
if( num[v]==n ) return true;
}
}
}
}
return false;
}
int main()
{
while( cin >> n >> m )
{
for(int i=1;i<=n;i++) scanf("%lf",&p[i]);
for(int i=1;i<=m;i++)
{
int l,r; double w; scanf("%d%d%lf",&l,&r,&w);
add(l,r,w);
}
double l=0,r=10000,ans=0;
while( r>=l+eps )
{
double mid = (l+r)/2.0;
if( spfa(mid) ) ans = mid,l = mid+eps;
else r = mid-eps;
}
printf("%.2f\n",l);
cnt=1;
for(int i=0;i<=n;i++) head[i]=0;
}
}