题目大意:给定一个有向图,每个点有一个权值$f[x]$,每一条边有一个权值$val[i]$,要求找出一个环,使得环上点权值之和除以换上所有边的权值的商最大。
这是一个经典的0/1分数规划问题,题目中的要求是$\frac {\sum f[i]} {\sum val[i]}$最大值
设$\frac {\sum f[i]} {\sum val[i]}=mid$
$\sum f[i]=mid \times \sum val[i]$
我们可以把两种权值都放到边上,我们假设一条边的另外一个权值为这条边上的父亲节点的权值。
然后就有$\sum f[i]-mid \times val[i] = 0$
$\sum mid \times val[i] - f[i]= 0$
我们可以先二分一个$mid$值,然后重建图,结构与原图相等,边权变成$\sum mid \times val[i] -f[i]$,然后用spfa算法判断与没有负环,有负环说明此时的$mid$小于最大值,则$l=mid$并更新答案,没有负环则$r=mid$,注意实数二分的精度限制。
Code:
#include<bits/stdc++.h>
#define maxn 250000
using namespace std;
int vis[maxn],cnt[maxn];
int n,m,u,v,w;
int tot1=0,pre1[maxn],son1[maxn],now1[maxn];
long double val1[maxn],val2[maxn],ans=0,d[maxn];
int tot2=0,pre2[maxn],son2[maxn],now2[maxn];
int dian[maxn];
queue<int> q;
void put1(int x,int y,long double z)
{
pre1[++tot1]=now1[x];
now1[x]=tot1;
son1[tot1]=y;
val1[tot1]=z;
}
void put2(int x,int y,long double z)
{
pre2[++tot2]=now2[x];
now2[x]=tot2;
son2[tot2]=y;
val2[tot2]=z;
}
int spfa()
{
memset(vis,0,sizeof(vis));
while(!q.empty())q.pop();
for(int i=1;i<=n;i++)
d[i]=1532534223;
int s=1;
d[s]=0;vis[s]=1;cnt[s]=0;
q.push(s);
while(!q.empty())
{
int x=q.front();q.pop();vis[x]=0;
for(int i=now2[x];i;i=pre2[i])
{
int v=son2[i];
if(d[v]>d[x]+val2[i])
{
d[v]=d[x]+val2[i];
cnt[v]=cnt[x]+1;
if(cnt[x]>n)return true;
if(!vis[v])
q.push(v),vis[v]=1;
}
}
}
return false;
}
void build(long double L)
{
memset(now2,0,sizeof(now2));
tot2=0;
for(int i=1;i<=n;i++)
{
for(int j=now1[i];j;j=pre1[j])
{
int v=son1[j];
put2(i,v,L*val1[j]-dian[i]);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&dian[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
put1(u,v,w);
}
long double l=0,r=1e6;
while(r-l>=0.00001)
{
long double mid=(l+r)/2;
build(mid);
if(spfa())
ans=max(ans,mid),l=mid;
else
r=mid;
}
printf("%.2Lf\n",ans);
return 0;
}