01分数规划 ——最有比率环 pku 3621 Sightseeing Cows

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

  

 

转载于:https://www.cnblogs.com/E-star/archive/2013/01/21/2870298.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值