http://poj.org/problem?id=3621
题意:有n个点m条有向边,每个点有一个点权val[i],边有边权w(i, j)。找一个环使得Σ(val) / Σ(w)最大,并输出。
思路:和之前的最优比率生成树类似,还是构造成这样的式子:F(L) = Σ(val[i] * x[i]) - Σ(w[i] * x[i] * L) = Σ(d[i]) * x[i] (d[i] = val[i] - w[i] * L),要使得L越大越好。
那么当L越大的时候,F(L)就越小,如果F(L)大于等于0的话,说明是可以改进的,于是要找最大的d[i],使得结果更优。这样就是求最长路并判正环。但是并不好处理。
那么反过来,F(L) = Σ(-d[i] * x[i]),就是要求得最短路并判负环,这样就可以直接套SPFA了。如果有负环的话,说明可以得到更优的结果。
还有起点是不固定的,于是一开始先把所有的点入队,然后再去求。
1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #include <algorithm> 5 #include <queue> 6 using namespace std; 7 #define N 10010 8 #define INF 0x3f3f3f3f 9 const double eps = 1e-9; 10 struct Edge { 11 int v, w, nxt; 12 } edge[N]; 13 int n, m, val[N], vis[N], tid[N], head[N], tot; 14 double dis[N]; 15 16 void Add(int u, int v, int w) { edge[tot] = (Edge) {v, w, head[u]}; head[u] = tot++; } 17 18 bool SPFA(double k) { 19 queue<int> que; 20 memset(tid, 0, sizeof(tid)); 21 for(int i = 1; i <= n; i++) que.push(i), vis[i] = 1, dis[i] = 0, tid[i] = 1; 22 while(!que.empty()) { 23 int u = que.front(); que.pop(); 24 tid[u]++; vis[u] = 0; 25 if(tid[u] > n) return false; 26 for(int i = head[u]; ~i; i = edge[i].nxt) { 27 int v = edge[i].v; 28 double d = (double)val[v] - k * edge[i].w; 29 if(dis[v] > dis[u] - d) { 30 dis[v] = dis[u] - d; 31 if(!vis[v]) vis[v] = 1, que.push(v); 32 } 33 } 34 } 35 return true; 36 } 37 38 double solve() { 39 double l = 0, r = INF; 40 while(fabs(r - l) > eps) { 41 double mid = (l + r) / 2; 42 if(SPFA(mid)) r = mid; 43 else l = mid; 44 } 45 return l; 46 } 47 48 int main() { 49 while(~scanf("%d%d", &n, &m)) { 50 tot = 0; memset(head, -1, sizeof(head)); 51 for(int i = 1; i <= n; i++) scanf("%d", &val[i]); 52 for(int i = 0; i < m; i++) { 53 int u, v, w; 54 scanf("%d%d%d", &u, &v, &w); 55 Add(u, v, w); 56 } 57 printf("%.2f ", solve()); 58 } 59 return 0; 60 }