Link with Game Glitch(负环)

题意

\(n\)个物品,\(m\)个转换,每\(ka_i\)\(b_i\)类物品可以换\(w \cdot kc_i\)\(d_i\)类物品。其中\(k\)为任意正实数。

求最大的\(0 \leq w \leq 1\)使得不存在一种转换方式可以得到无限多的某类物品。

题目保证当\(w = 1\)时,必然存在一种转换方式使得某类物品可以得到无限多个。

(题目翻译来源于emofunc的讲解PPT)

题目链接:https://ac.nowcoder.com/acm/contest/33187/D

数据范围

\(2 \leq n \leq 1000\)
\(2 \leq m \leq 2000\)

思路

由于\(k\)为任意正实数,因此我们可以计算每个\(b_i\)可以换多少个\(d_i\),答案是\(\frac{w \cdot c_i}{a_i}\)

然后我们再考虑什么情况下某类物品可以得到无限多个,不妨设该类物品为\(p_0\),且初始数量为\(1\)。使用\(p_0\)换取若干\(p_1\),然后\(p_1\)再换取若干\(p_2\),依此类推,最终\(p_{k-1}\)换取若干\(p_k\)\(p_k\)再换取若干\(p_0\)。如果一轮循环之后的\(p_0\)个数大于\(1\),则最终\(p_0\)可以无限获得。

一轮循环之后的\(p_0\)个数为\(\frac{wc_1}{a_1} \cdot \frac{wc_2}{a_2} \cdots \frac{wc_k}{a_k} \cdot \frac{wc_0}{a_0}\)。为了方便计算,我们将个数取对数,可以将条件转化为:\(\sum\limits_{i=0}^k \log (\frac{wc_i}{a_i}) > 0\),即:\(\sum\limits_{i=0}^k -\log (\frac{wc_i}{a_i}) < 0\)

因此,我们在\(b_i\)\(d_i\)之间连一条权值为\(-\log(\frac{c_i}{a_i})\)的有向边。然后二分\(w\)。最后使用spfa算法判断图中是否存在负环即可。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>

using namespace std;

const int N = 2010;
const double eps = 1e-8;

int n, m;
int h[N], e[N], ne[N], idx;
double w[N], d[N];
bool st[N];
int cnt[N];

void add(int a, int b, double c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

bool check(double x)
{
    queue<int> que;
    for(int i = 1; i <= n; i ++) {
        que.push(i);
        st[i] = true;
        cnt[i] = 0;
        d[i] = 0;
    }
    double k = log(x);
    while(que.size()) {
        int t = que.front();
        que.pop();
        st[t] = false;
        for(int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if(d[j] > d[t] - w[i] - k) {
                d[j] = d[t] - w[i] - k;
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n) return false;
                if(!st[j]) {
                    st[j] = true;
                    que.push(j);
                }
            }
        }
    }
    return true;
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i ++) {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        add(b, d, log(1.0 * c / a));
    }
    double l = 0.0, r = 1.0;
    for(int i = 0; i < 100; i ++) {
        double mid = (l + r) / 2;
        if(check(mid)) l = mid;
        else r = mid;
    }
    printf("%.10f\n", l);
    return 0;
}
posted @ 2022-08-19 22:36  pbc的成长之路  阅读(21)  评论(0编辑  收藏  举报