http://poj.org/problem?id=3621
题意:
给定一张图,边上有花费,点上有收益,点可以多次经过,但是收益不叠加,边也可以多次经过,但是费用叠加。求一个环使得收益和/花费和最大,输出这个比值。
思路:(转载)
首先的一个结论就是,不会存在环套环的问题,即最优的方案一定是一个单独的环,而不是大环套着小环的形式。这个的证明其实非常的简单,大家可以自己想一下(提示,将大环上的收益和记为x1,花费为y1,小环上的为x2,y2。重叠部分的花费为S。表示出来分类讨论即可)。有了这个结论,我们就可以将花费和收益都转移到边上来了,因为答案最终一定是一个环,所以我们将每一条边的收益规定为其终点的收益,这样一个环上所有的花费和收益都能够被正确的统计。
解决了蛋疼的问题之后,就是01分数规划的部分了,我们只需要计算出D数组后找找有没有正权环即可,不过这样不太好,不是我们熟悉的问题,将D数组全部取反之后,问题转换为查找有没有负权环,用spfa或是bellman_ford都可以。这道题目就是典型的不适合用Dinkelbach,记录一个负权环还是比较麻烦的,所以二分搞定。
上面讲的挺清晰的。。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <List> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define Min(a,b) (a) > (b)? (b):(a) #define Max(a,b) (a) > (b)? (a):(b) #define ll __int64 #define inf 0x7f7f7f7f #define MOD 1073741824 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define M 5007 #define N 1007 using namespace std; //freopen("din.txt","r",stdin); const double eps = 1e-6; struct node { int u,v; double w; int next; }g[M]; int head[N],ct; double val[N]; double dis[N]; bool vt[N]; int que[1000003],l,r; int num[N]; int n,m; int dblcmp(double x) { if (x > eps) return 1; else if (x < -eps) return -1; else return 0; } void add(int u,int v,int w) { g[ct].v = v; g[ct].w = w; g[ct].next = head[u]; head[u] = ct++; } int spfa(double mid) { int i; l = r = 0; for (i = 0; i < n; ++i) { dis[i] = inf; num[i] = 0; vt[i] = false; } dis[0] = 0; que[r] = 0; num[0] = 1; vt[0] = true; while (l <= r) { int u = que[l++]; vt[u] = false; for (int j = head[u]; j != -1; j = g[j].next) { int i = g[j].v; if (dis[i] > dis[u] + mid*g[j].w - val[i]) { dis[i] = dis[u] + mid*g[j].w - val[i]; if (!vt[i]) { vt[i] = true; que[++r] = i; if (++num[i] > n) return 1;//进入队列的次数超过n次,表示存在负环 } } } } return 0; } int main() { int i; double z; int x,y; while (~scanf("%d%d",&n,&m)) { CL(head,-1); ct = 0; double l = 0; double r = 0; for (i = 0; i < n; ++i) { scanf("%lf",&val[i]); r += val[i]; } double pathMin = inf; for (i = 0; i < m; ++i) { scanf("%d%d%lf",&x,&y,&z); add(x - 1,y - 1,z);//稀疏图有临界表存 pathMin = min(pathMin,z); } r /= pathMin; double mid = 0,ans = 0; while (dblcmp(l - r) < 0) { mid = (l + r)/2; if (spfa(mid)) { ans = mid; l = mid; } else r = mid; } printf("%.2lf\n",ans); } return 0; }