题目链接:http://poj.org/problem?id=3621
题意:
给你L个点,P条边,每个点和边都有各自的权值,现在要你求一个环,使得这个环的点权/边权最大。
做法:
自己做当然是。。。不会做啦。。这个坑已经放在这里很久,之前做了最优比率生成树的,这样三个01分数规划的专题就算完成了。因为我们要求一个最大的,那么我们设这个比例为x,那么我们会有一个公式,所以我们要二分一个最优值x,假设由这个公式出来的值是正的,那么就说明还有一个更大的x值满足要求,那么就把l=mid继续二分,否则就把r=mid。可是如果我们要判断是否存在一个环满足大于0有点困难,因为spfa可以判断负环,那么我们就可以把公式改一下,令,如果存在一个负环,那么就是令l=mid;
这里我在写之前一直有个疑问,不是说所有的地点只有第一次走的时候有效吗,那为什么每条路里面都要减掉happy[i]呢,想了一会儿,然后发现,因为每个点只能在一个环中,所以每次如果松弛的时候更新到这个点的话,那么就说明还有一个更符合要求的环需要这个点了,而如果我们要这个环,那么这个点就只会被遍历到一次,所以才要每一条边都要减掉happy[i]。
#include<queue>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn=3005;
const int inf=0x3f3f3f3f;
const double eps=1e-7;
int m,n,head[maxn],now,flag,vis[maxn],ct[maxn];
double dist[maxn],happy[maxn],w,maxhp,minhp,maxti,minti;
struct node{
int to,next;
double w;
}e[maxn*120];
void add(int u,int v,double w){
e[now].to=v,e[now].next=head[u];
e[now].w=w; head[u]=now++;
}
int spfa(int x,double mid){
queue<int> q;
dist[x]=0;
vis[x]=1; ct[1]=1;
q.push(x);
while(!q.empty()){
int u=q.front(); q.pop();
if(++ct[u]>n) return 1;
vis[u]=0;
for(int i=head[u];~i;i=e[i].next){
int t=e[i].to;
double w=mid*e[i].w-happy[t];;
if(dist[t]-w>dist[u]){
dist[t]=w+dist[u];
if(!vis[t]){
vis[t]=1;
q.push(t);
}
}
}
}
return 0;
}
int ck(double mid){
memset(ct,0,sizeof(ct));
memset(vis,0,sizeof(vis));
memset(dist,125,sizeof(dist));
dist[1]=0;
return spfa(1,mid);
}
int main(){
int x,y;
memset(head,-1,sizeof(head));
maxhp=-1,maxti=-1,minhp=inf,minti=inf;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lf",&happy[i]);
maxhp=max(maxhp,happy[i]);
minhp=min(minhp,happy[i]);
}
for(int i=0;i<m;i++){
scanf("%d%d%lf",&x,&y,&w);
add(x,y,w);
maxti=max(maxti,w);
minti=min(minti,w);
}
double l=minhp/maxti,r=maxhp/minti,mid;
while(r-l>eps){
mid=(l+r)/2;
if(ck(mid)) l=mid;
else r=mid;
}
printf("%.2f\n",mid);
return 0;
}