题意:给定一个n(2 <= n <= 1000)个点,m(2 <= m <= 5000)条边的有向图,给定每个点的点值f(i)和每条边的权值w(i),求一个环使得路径上点权和除以边权和最大。
简单来说就是最优比率生成环。
分析:
这是一道0/1分数规划的题,我们记最优解为ans,则对于所有环ans >= ∑f(i) / ∑w(i),变形得ans * ∑w(i) - ∑f(i) >= 0,∑(ans * w(i)) - ∑f(i) >= 0,∑(ans * w(i) - f(i)) >= 0.
我们采用二分答案的方法,设二分的值为k.
之后构建一个新图,边权值为k * w(i) - 入点/出点f(i),如果k < ans,则存在至少一个负环,k >= ans,则无负环,用SPFA判负环即可,需要注意的是源点不要从1开始(虽然这道题数据水从1开始也能过),因为环可能和1不连通。
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
bool inq[1005];
int n, m, e, x[5005], y[5005], z[5005], f[1005], hd[1005], cnt[1005];
double d[1005];
struct Edge {
int to, nxt;
double w;
}edge[5005];
void add(int x, int y, double z) {
edge[++e].to = y;
edge[e].w = z;
edge[e].nxt = hd[x];
hd[x] = e;
}
bool spfa() {
queue<int> q;
for(int i = 1; i <= n; i++) q.push(i), d[i] = 0, inq[i] = 1;
memset(cnt, 0, sizeof cnt);
while(!q.empty()) {
int u = q.front(); q.pop();
inq[u] = 0;
for(int i = hd[u]; i; i = edge[i].nxt) {
Edge &v = edge[i];
if(d[v.to] > d[u] + v.w) {
d[v.to] = d[u] + v.w;
if(++cnt[v.to] > n) return true;
if(!inq[v.to]) q.push(v.to), inq[v.to] = 1;
}
}
}
return false;
}
bool ok(double ans) {
e = 0;
memset(hd, 0, sizeof hd);
for(int i = 1; i <= m; i++) add(x[i], y[i], ans * z[i] - f[x[i]]);
return spfa();
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &f[i]);
for(int i = 1; i <= m; i++) scanf("%d%d%d", &x[i], &y[i], &z[i]);
double l = 0, r = 1000;
while(r - l > 1e-4) {
double mid = (l+r) / 2;
if(ok(mid)) l = mid;
else r = mid;
}
printf("%.2f", l);
return 0;
}