POJ 3621 Sightseeing Cows(01分数规划之最优比率生成环)

本文针对POJ 3621题目,介绍了一个基于SPFA算法解决的最大收益比路径问题。通过对边权和点权的巧妙转换,利用二分法结合SPFA检测负环的方法,实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题地址:http://poj.org/problem?id=3621

题意:给定一张图,边上有花费,点上有收益,点可以多次经过,但是收益不叠加,边也可以多次经过,但是费用叠加。求一个环使得收益和/花费和最大,输出这个比值。

思路:说下我遇到的问题和我的理解

v v 是点的集合,e是边的集合
假设 ans a n s 是最优解,那么 ΣV[i]Σe[i]<=ans Σ V [ i ] Σ e [ i ] <= a n s ,那么化简之后, Σanse[i]v[i]>=0 Σ a n s ∗ e [ i ] − v [ i ] >= 0 ,我们可以知道,前面的式子和我们所知的01规划非常像.那么我么就可以令每条边的权值都转化为 anse[i]v[i] a n s ∗ e [ i ] − v [ i ] ,这样子把比边权和点权都转化在了一起,(其中v[i]是边的起点还是终点都是可以的).经过这样子转化为之后,我们就将直接跑spfa判断了

那么接下来的问题就是如何去进行二分了
我们可以发现,边权之和就和前面的 Σanse[i]v[i]>=0 Σ a n s ∗ e [ i ] − v [ i ] >= 0 这个式子就很像了.
进一步转化,如果这个环是负环,那就说明ans>k,那么二分答案要增加.
如果没有负环,那么就说明ans

#include <cmath>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <set>
#include <cctype>
#define eps 1e-8
#define INF 0x3f3f3f3f
#define MOD 1e9+7
#define PI acos(-1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define CLR(x,y) memset((x),y,sizeof(x))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 5;
struct node {
    int v, w, nxt;
} e[maxn];
int head[maxn];
int tot;
void init() {
    tot = 0;
    CLR(head, -1);
}
void add_edge(int u, int v, int w) {
    e[tot].v = v;
    e[tot].w = w;
    e[tot].nxt = head[u];
    head[u] = tot++;
}
int arr[maxn];
int n, m;
int vis[maxn];
int cnt[maxn];
double dis[maxn];
int spfa(double g) {
    queue<int>Q;
    for (int i = 1; i <= n; ++i) {//可能会有不连通的,所以要将所有的点入队列
        dis[i] = 0;
        vis[i] = 1;
        cnt[i] = 1;
        Q.push(i);
    }
    while (!Q.empty()) { //找负环
        int u = Q.front();
        Q.pop();
        vis[u] = 0;
        for (int i = head[u]; ~i; i = e[i].nxt) {
            int v = e[i].v;
            double w = g * e[i].w - arr[u];//将边和点的权值全看做是边的,那就可以转化为spfa求负环
            //这边arr[u/v]都可以,区别只是在于你是定义将起点和边的收益绑在一起还是将终点的边的收益绑在一起
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                if (!vis[v]) {
                    vis[v] = 1;
                    Q.push(v);
                    if (++cnt[v] > n) return 1;
                }
            }
        }
    }
    return 0;
}


int main() {
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= n; i++) {
        scanf("%d", &arr[i]);
    }
    for(int i = 1; i <= m; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add_edge(u, v, w);
    }
    double l = 0;
    double r = INF;
    for(int i = 1; i <= 50; i++) {
        double mid = (l + r) / 2;
        if(spfa(mid)) l = mid;//如果出现负环,那么就说明当前mid<ans
        else r = mid;
    }
    printf("%.2f\n", r);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值