ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P1768 天路

2022-05-14 14:31:32  阅读:127  来源: 互联网

标签:cnt P1768 int text sum cost joy


一种完全使用裸 SPFA 算法的做法。

一道分数规划题,实际上是最短路的灵活运用。

题意

题目话很多,简单来说就是:

\(n\) 个点,\(m\) 条边,每条边有两个属性,分别为花费 \(\text{cost}\) 和收益 \(\text{joy}\)。求出一个环使得环上的 \(\dfrac{\sum\text{joy}}{\sum \text{cost}}\) 的值最大。

思路

如果我们令答案为 \(p\),则对于图上任意一个环,都有:

\[\dfrac{\sum\text{joy}}{\sum \text{cost}}\le p \]

将不等式左右两边乘上 \(\sum\text{cost}\),则:

\[\sum\text{joy}\le p\times\sum\text{cost} \]

换个位置可得:

\[p\times \sum\text{cost}-\sum\text{joy}\ge 0 \]

也就是说,\(p\) 是正确的答案时,图上不会出现负环。

对于第 \(i\) 条边而言,在跑 SPFA 算法的时候,他实际的边权应该是

\[p\times \text{cost}_i-\text{joy}_i \]

另外,我们可以发现,题目要求的是一个式子的值最大。观察 \(p\times \sum\text{cost}-\sum\text{joy}\) 发现这个式子是具有单调性的,那么就可以使用二分减少计算次数(从 \(200\) 次计算下降到了 \(\log_2(200)\approx8\) 次)

而且,在这道题中,题目并没有告诉我们起点是那个点。因此,我们需要建立一个超级源点(我的代码中是 \(0\) 号点),然后将他与其他 \(n\) 个点连接一条不会对答案产生影响的边。

最后,我罗列一下需要注意的几个点:

  • SPFA 的 \(\text{d}\) 数组需要是 bool 类型的,而且不能使用 memset 进行赋值,必须使用 for 循环暴力赋值。
  • 二分左端点初始为 \(1\),右端点初始设置为一个大于等于 \(200\) 的数。
  • 当二分结束后,左端点是等于 \(0\) 时,那么代表无解,输出 \(-1\)。

你以为结束了?并没有。

当你交上上面思路写出的代码时,你会发现你只有 90 分。

上面的思路唯一可以再减少操作步数的就只有 SPFA 内部了。我们设置一个值 \(\text{delta}\) 表示当一个点入队次数上限,进行人肉二分,发现当 \(\text{delta}\) 等于 \(10\) 的时候就可以通过此题,而且跑的飞快,总共只有 67ms,成功进入最优解第一页。

代码

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
#include<iomanip>
using namespace std;

const int maxn = 7010;
const double eps = 1e-3;

int n, m;

struct node {
    int v, joy, cost;
    node(int vv, int jjoy, int ccost) {
        v = vv, joy = jjoy, cost = ccost;
    }
};

std::vector<node> g[maxn];

void add(int u, int v, int joy, int cost) {
    g[u].push_back(node(v, joy, cost));
}

double d[maxn];
bool inqueue[maxn];
int cnt[maxn];

const int delta = 30;

bool spfa(double p) {
    for (int i = 0; i < maxn; i++) {
        d[i] = 1e9;
    }
    memset(inqueue, 0, sizeof(inqueue));
    memset(cnt, 0, sizeof(cnt));
    queue<int> q;
    q.push(0);
    d[0] = 0;
    cnt[0]++;
    inqueue[0] = true;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inqueue[u] = false;
        if (cnt[u] > delta) {
            return true;
        }
        for (int i = 0; i < g[u].size(); i++) {
            int v = g[u][i].v;
            if (d[v] > d[u] + p * g[u][i].cost - g[u][i].joy) {
                d[v] = d[u] + p * g[u][i].cost - g[u][i].joy;
                if (!inqueue[v]) {
                    inqueue[v] = true;
                    q.push(v);
                    cnt[v]++;
                    if (cnt[v] > delta) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v, joy, cost;
        cin >> u >> v >> joy >> cost;
        add(u, v, joy, cost);
    }
    for (int i = 1; i <= n; i++) {
        add(0, i, 0, 0);
    }
    double l = 0, r = 200.0;
    while (l + eps < r) {
        double mid = (l + r) / 2.0;
        if (spfa(mid)) {
            l = mid;
        } else {
            r = mid;
        }
    }
    if (l == 0) {
        cout << -1 << endl;
    } else {
        cout << fixed << setprecision(1) << l << endl;
    }
    return 0;
}

标签:cnt,P1768,int,text,sum,cost,joy
来源: https://www.cnblogs.com/luogu-int64/p/16270049.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有