PKU 3621-Sightseeing Cows [分数规划/spfa]
题目大意:有一个单向图,可以从任意一点出发,回到该点,每条路都会耗费一定的时间ti。每到一个新的点都会得到一定的分数fi。求一条路使得∑fi/∑ti最大。
题目分析:我们设最优解经过的城市为环 n1->n2->...nk->n1,我们可以猜测最优解的路上不会重复经过一个点(除去出发点)。简略证明:
假设一个非出发点k被重复经过了,那么k点至少连接了两个环。我们设第一个环的解为:x1 = F1/T1 ,第二个环的解为:x2 = F2/T2。总的解为: x3 = (F1+F2-fk)/(T1+T2)<max(x1,x2)。
那么最优解为 x' = (fn1+fn2+.....fnk)/(e(n1,n2),e(n2,n3)+....e(nk,n1))。 x' = F/T -> T * x' - F = 0。
为了求解出这个最优值x',我们设函数 g(x)=T*x-F。这是一个单调函数。所以可以用二分求出答案。
/*
简略证明:
我们设T(y),F(y)为函数(当y不同,表示选取不同的环),设x1<x2,y1 为使 g(x1) 取得最小值的解选择,则:
g(x1) = max(T(y) * x1 - F(y)) = T(y1) * x1 - F(y1) < T(y1)*x2 - F(y1) <=max( T(y)*x2 - F(y) ) = g(x2)
*/
当g(x)>0时,x > x'
当g(x)<0时,x < x'
当g(x)=0时,x = x'
我们可以改变所有边权为 : t*x-f,用spfa求解是否存在负环(g(x)<0),存在增大x,否则减小x
我们把所有没松弛过的点,当成源点,spfa一次...但这题似乎直接对1点松弛也过了,是数据问题??
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> #include<queue> using namespace std; #define MAXN 1010 #define MAXE 5050 #define inf 1000000000.0 #define SET(a,b) memset(a,b,sizeof(a)) #define EPS 1e-3 int next[MAXE],p[MAXE],st[MAXN],e; double dis[MAXN],f[MAXN],c[MAXE]; int used[MAXN],cnt[MAXN]; void add(int a,int b,double d) { next[e]=st[a]; p[e]=b; c[e]=d; st[a]=e++; } bool spfa(double mid,int S,int N) { int i,j,k; queue<int> Q; for(i=1;i<=N;i++){ dis[i]=inf; used[i]=0; } dis[S]=0.0;Q.push(S); while(!Q.empty()) { int u=Q.front(); Q.pop(); used[u]=0; for(int v=st[u];v!=-1;v=next[v]) { double w=c[v]*mid-f[p[v]]; if(dis[p[v]]-dis[u]-w>-EPS) { dis[p[v]]=dis[u]+w; if(!used[p[v]]) { used[p[v]]=1; cnt[p[v]]++; if(cnt[p[v]]>N)return true; Q.push(p[v]); } } } } return false; } int main() { SET(st,-1);e=0; int N,M; scanf("%d%d",&N,&M); int i,j,k; double l=0,r=0; for(i=1;i<=N;i++) { scanf("%lf",f+i); r+=f[i]; } for(i=1;i<=M;i++) { int x,y; double z; scanf("%d%d%lf",&x,&y,&z); add(x,y,z); } while(r-l>EPS) { double mid=(l+r)/2; SET(cnt,0); bool flag=false; for(i=1;i<=N;i++) if(!cnt[i]) { flag=spfa(mid,i,N); if(flag)break; } if(flag)l=mid; else r=mid; } printf("%.2f\n",l); return 0; }