01分数规划,简单的来说,就是有一些二元组(si,pi),从中选取一些二元组,使得∑si / ∑pi最大(最小)。
这种题一类通用的解法就是,我们假设x = ∑si / ∑pi的最大(小)值,那么就有x * ∑pi = ∑si ,即∑si - x * ∑pi= 0。也就是说,当某一个值x满足上述式子的时候,它就是要求的值。我们可以想到枚举……不过再想想,这个可以二分答案。
所以我们直接二分答案,当上述式子>0,说明答案小了,<0则说明答案大了,这样计算即可。
(以上来自网络)
思路:设环上各点的权值为f[i],环上各边的权值为t[k],由题目可的Σf[i]/Σt[k]==mid,使得mid的值最大,换一下就是找到使得Σf[i]-mid*Σt[k]>0成立的最大mid的值。
找mid的过程就是二分的过程。
f[i]-mid*t[k]含义:点i的权值减去mid乘点i的出边的边权。
对于上面的Σf[i]-mid*Σt[k],建立一个新图使得边权为f[i]-mid*t[k],在新图里面进行找最长路的操作,当mid取值太小时,Σf[i]-mid*Σt[k]会小于0,此时会存在负环,但因为是求最长路,并不会有某一个点的入队次数超过n,最后会返回false,存在正环的时候就会返回true。true说明mid取小了,使二分边界 l =mid ,返回false就使r=mid,最后使得abs(r-l)小于1e-4,可以确保保留两位小数的数值准确。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1010,M=5010;
int n,m;
int wf[N];
int h[N],wt[M],idx,ne[M],e[M];
double dist[N];
bool st [N];
int q[N],cnt[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],wt[idx]=c,h[a]=idx++;
}
bool check(double mid)
{
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
memset(dist,0,sizeof dist);
int hh=0,tt=0;
for(int i=1;i<=n;i++)
{
q[tt++]=i;
st[i]=true;
}
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+wf[t]-mid*wt[i])
{
dist[j]=dist[t]+wf[t]-mid*wt[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)
scanf("%d",&wf[i]);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
double l=0,r=1e6;
while(r-l>1e-4)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2lf\n",r);
return 0;
}