題目鏈接:https://www.luogu.com.cn/problem/P2868
題目大意
爲了獎勵奶牛們的辛勤勞動,農夫約翰決定帶他的奶牛們一起在城市中旅遊。與此同時,奶牛們也在考慮如果高效地利用它們的閒暇時光。
在城市中有 \(L(2 \le 1000)\) 個景點(編號從 \(1\) 到 \(L\))以及 \(P(2 \le P \le 5000)\) 條牛行道連接這些景點。農夫約翰開車帶着奶牛們到達其中的一個景點,以這個景點作爲起點,奶牛們會從起點下車並沿着牛行道到達其它的一些景點,並最終回到起點。然後農夫約翰會把他們全都裝入車中並返回農場。與此同時,由於牛行道都過於下載,政府不得不將這些牛行道都設計成單行道。
奶牛們到達第 \(i\) 個景點將會收穫 \(F_i(1 \le F_i \le 1000)\) 點歡樂值。第 \(i\) 條牛行道從第 \(L1_i\) 個景點出發到達第 \(L2_i\) 個景點(方向爲 \(L1_i \rightarrow L2_i\)),奶牛們在這條牛行道上通過需要花費 \(T_i(1 \le T_i \le 1000)\) 的時間。
爲了最高效地利用它們的閒暇時光,奶牛們希望最大化它們的平均歡樂值。並且對於任意一個景點,只有在第一次到達的時候能夠獲得這個景點的歡樂值(奶牛們可能會多次到達某一個景點,但第二次開始就不會獲得這個景點的歡樂值)。同時,農夫約翰會讓奶牛們到達至少兩個景點,以讓它們進行一些鍛鍊。
幫助奶牛們計算平均歡樂值的最大值,即奶牛們經過的那些景點的歡樂值之和與通過牛行道所花費的時間之和的比值的最大值。
輸入格式
第一行:兩個整數 \(L\) 和 \(P\),以一個空格分隔。
第 \(2 \sim L+1\) 行:第 \(i+1\) 包含一個整數 \(F_i\)。
第 \(L+2 \sim L+P+1\) 行:第 \(L+i+1\) 行包含用於描述第 \(i\) 條牛行道的三個整數 \(L1_i\), \(L2_i\) 和 \(T_i\)。
輸出格式
輸出平均歡樂值的最大值,保留兩位小數。如果奶牛不能夠到達至少 \(2\) 個城市,輸出 \(0\)。
解題思路
01分數規劃 + SPFA判負環。
每一條邊邊權爲 \(-(a_i - mid \times b_i)\),其中 \(a_i\) 表示第 \(i\) 條邊的終點的歡樂值,\(b_i\) 表示走第 \(i\) 條邊所需花費的時間。如果存在負環則說明存在 \(\sum a_i - mid \times b_i \gt 0\)。
關於 “最優答案不存在一個點經過兩次” 的證明,請參考 木木!大佬的博客
示例代碼:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010, maxm = 5050;
struct Edge {
int v, w, nxt;
Edge() {};
Edge(int _v, int _w, int _nxt) { v = _v; w = _w; nxt = _nxt; }
} edge[maxm];
stack<int> stk;
int n, m, cnt[maxn], f[maxn];
int head[maxn], ecnt;
void init() {
ecnt = 0;
memset(head, -1, sizeof(int)*(n+1));
}
void addedge(int u, int v, int w) {
edge[ecnt] = Edge(v, w, head[u]); head[u] = ecnt ++;
}
double dis[maxn];
bool inq[maxn];
bool check(double mid) { // spfa判負環
while (!stk.empty()) stk.pop();
for (int i = 1; i <= n; i ++) {
dis[i] = 0;
cnt[i] = 1;
stk.push(i);
inq[i] = true;
}
while (!stk.empty()) {
int u = stk.top();
stk.pop();
inq[u] = false;
for (int i = head[u]; i != -1; i = edge[i].nxt) {
int v = edge[i].v, w = edge[i].w;
if (dis[v] > dis[u] - (f[v] - mid * w)) {
dis[v] = dis[u] - (f[v] - mid * w);
if (!inq[v]) {
inq[v] = true;
stk.push(v);
cnt[v] ++;
if (cnt[v] >= n) return true;
}
}
}
}
return false;
}
int main() {
cin >> n >> m;
init();
for (int i = 1; i <= n; i ++) cin >> f[i];
while (m --) {
int u, v, w;
cin >> u >> v >> w;
addedge(u, v, w);
}
double L = 0, R = 1000;
while (R - L > 1e-3) {
double mid = (L + R) / 2;
if (check(mid)) L = mid;
else R = mid;
}
printf("%.2lf\n", L);
return 0;
}