版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
- 题目链接:acwing361
- 题意概括:给定一个有向图,点权、边权,求一个环使得它的点权和除以边权和最大。
- 思路:01分数规划的思想,先二分L值,然后跑最长路spfa判正环(相当于01分数规划中使得相减后的权值最大)
- spfa判正环:记录一个数组cnt[i],表示从1到i的最长路中有cnt[i]条边。每次更新dis值时更新cnt:cnt[e[i].to]=cnt[zz]+1。当cnt大于等于n(注意,这里是点数)说明有负环。
- 注意:存在易错点,在代码中标出。
#include<bits/stdc++.h>
using namespace std;
struct node{
int to,nxt,val;
}e[10005];
int n,m,f[1005],tot,head[1005];
void build(int a,int b,int c)
{
e[++tot].to=b;
e[tot].val=c;
e[tot].nxt=head[a];
head[a]=tot;
}
int cnt[1005];
double dis[1005];
bool vis[1005],inq[1005];
queue<int>q;
bool check(double v)
{
memset(vis,0,sizeof(vis));
memset(dis,-0x3f,sizeof(dis));//最长路要把dis初值赋值为负
memset(inq,0,sizeof(inq));//一定要清零!因为在跑spfa时中途要退出
for(int zz,i=1;i<=n;i++)
{
if(!vis[i])
{
while(!q.empty())q.pop();
dis[i]=0;
cnt[i]=0;
q.push(i);
while(!q.empty())
{
zz=q.front();
q.pop();
inq[zz]=false;
vis[zz]=true;
for(int j=head[zz];j;j=e[j].nxt)
{
if(dis[e[j].to]<dis[zz]+f[e[j].to]-e[j].val*v)
//因为是点权和除以边权和,所以注意距离的转移
{
dis[e[j].to]=dis[zz]+f[e[j].to]-e[j].val*v;
cnt[e[j].to]=cnt[zz]+1;
if(cnt[e[j].to]>=n)
return true;
if(!inq[e[j].to])
{
inq[e[j].to]=true;
q.push(e[j].to);
}
}
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&f[i]);
for(int a,b,c,i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
build(a,b,c);
}
double l=0,r=100000,mid;
while(r-l>1e-7)
{
mid=(l+r)/2;
if(check(mid))
{
l=mid;
}
else
r=mid;
}
printf("%.2lf",l);
}