01分数规划
题目
思路
在图论问题中,求(一堆的和 / 一堆的和)这样的问题我们称之为\(01\)分数规划,一般我们都采用二分来做。
\(\frac{\sum f_i}{\sum t_i}\) , 其中\(f_i\)表示点权,\(t_i\)表示边权。
\[\frac{\sum f_i}{\sum t_i} > mid \\
\sum f_i > mid \sum t_i \\
\sum f_i - mid \sum t_i > 0 \\
\sum (f_i - mid\times t_i) > 0 \\
\]
我们再将边权置为\(w(u \rightarrow v) = f_u - mid \times t_{u, v}\),check函数就可以写成图中是否存在正环来判断是否为true, 由于判断的是存在,我们只需要求最长路即可。
这里需要注意一下,求负环是否存在我们用最短路, 求正环是否存在我们用最长路,既保证尽量小和尽量大。
最后,确定答案区间,我们只需要二分答案即可。
Code
#include <iostream>
#include <queue>
#include <cstring>
using i64 = long long;
const int N = 1100, M = 1e4 + 10;
int n, m;
int h[N], e[M], ne[M], t[M], f[N], idx;
double dist[N];
int cnt[N];
bool st[N];
void add(int a, int b, int c) {
ne[idx] = h[a], h[a] = idx, e[idx] = b, t[idx ++] = c;
}
bool spfa(double mid) {
memset(dist, 0, sizeof dist);
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
std::queue<int> q1;
for (int i = 1; i <= n; i ++) {
q1.push(i);
st[i] = true;
}
while (q1.size()) {
int u = q1.front(); q1.pop();
st[u] = false;
// std::cout << u << "\n";
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (dist[v] < dist[u] + (f[u] - t[i] * mid)) {
dist[v] = dist[u] + (f[u] - t[i] * mid);
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) {
return true;
}
if (!st[v]) {
q1.push(v);
st[v] = true;
}
}
}
}
return false;
}
int main() {
memset(h, -1, sizeof h);
std::cin >> n >> m;
for (int i = 1; i <= n; i ++) std::cin >> f[i];
for (int i = 1; i <= m; i ++) {
int a, b, c;
std::cin >> a >> b >> c;
add(a, b, c);
// add(b, a, c);
}
// for (int i = 1; i <= n; i ++) {
// add(0, i, 0);
// }
double l = 0, r = 1e6;
while (r - l > 1e-4) {
double mid = (l + r) / 2;
if (spfa(mid)) l = mid;
else r = mid;
}
printf("%.2lf", l);
}