题意:给定一个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; }