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;
}