相关题解参见这里,这一类问题这里都讲到了,很全面。
/*
* poj3621 AC 625ms
* 所谓的01分数规划+spfa判断正(负)环+二分枚举答案
* 为什么就那么慢,那个pascal的0ms是直接打印的答案吧,快得逆天了啊!
* 经常会纠结这种问题:
* 对一个任务选择一种方案,存在不同的价值与成本,要求(价值/成本)最大的方案。
*
* 事实上这都是 运筹学 的关于规划的内容,所以要去看书了。
*
* 除开规划部分,spfa判断正(负)环时要注意图可能并不连通,所以要枚举每一个点作为起点,
* 同时用vis[]记录已经访问过的点,避免重复计算,类似问题之前也遇到过。
* */
#include<stdio.h>
#include<memory.h>
#include<queue>
#include<cmath>
#define INF 1000000000
using namespace std;
int n,p,f[1005];
struct EDGE
{
int v,t,next;
}edge[5005];
int head[1005],tot,num[1005];
double d[1005];
inline void init()
{
// FILE* fin;
// fin = fopen("d.in","r");
scanf("%d%d",&n,&p);
// fscanf(fin,"%d%d",&n,&p);
int i,j,k,m;
memset(f,0,sizeof(f));
for(i=1;i<=n;i++)
scanf("%d",&f[i]);
// fscanf(fin,"%d",&f[i]);
tot = 0;
memset(edge,0,sizeof(edge));
memset(head,0,sizeof(head));
for(i=1;i<=p;i++)
{
scanf("%d%d%d",&j,&k,&m);
// fscanf(fin,"%d%d%d",&j,&k,&m);
edge[++tot].v = k;
edge[tot].t = m;
edge[tot].next = head[j];
head[j] = tot;
}
// fclose(fin);
return;
}
inline bool spfa(double l)
{
int i,j,k;
bool inq[1005],vis[1005];
double w;
memset(vis,false,sizeof(vis));
queue<int> q;
for(j=1;j<=n;j++) //注意,可能存在离散的点。
{
if(vis[j]) continue; //访问过的点就跳过。
memset(num,0,sizeof(num));
memset(inq,false,sizeof(inq));
memset(d,0,sizeof(d));
d[j] = 0,q.push(j),inq[j] = true,num[j] = 1;
while(!q.empty())
{
k = q.front();
q.pop();
inq[k] = false,vis[k] = true;
for(i=head[k];i;i=edge[i].next)
{
w = f[k]-l*edge[i].t; //将点权移到边权上,为什么可以对应?
if(d[edge[i].v]<d[k]+w) //是判断是否有正环
{
d[edge[i].v] = d[k]+w;
if(!inq[edge[i].v])
{
num[edge[i].v]++;
if(num[edge[i].v]>n) return true; //有正环
q.push(edge[i].v);
inq[edge[i].v] = true;
}
}
}
}
}
return false; //无正环
}
inline void solve()
{
double mid,l,r,Eps = 0.000001;
l = 0,r = 5000;
do
{
mid = (l+r)/2; //F(L) = a[i]-mid*b[i] mid即为答案,函数为减函数。
if(spfa(mid))
l = mid; //有正环,则答案需要增大,移动下界。
else r = mid; //无正环,则答案需要减小,移动上界。
}while(abs(r-l)>Eps);
if(l>Eps) printf("%.2f\n",l); else printf("0\n");
}
int main()
{
init();
solve();
return 0;
}