题目链接: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;
}