题意:给定一个n(2 <= n <= 1000)个点,m(2 <= m <= 5000)条边的有向图,给定每个点的点值f(i)和每条边的权值w(i),求一个环使得路径上点权和除以边权和最大。
还是要推公式。
设最大值为ans
那么ans>=∑f(i)/∑w(i);
∑w(i)*ans>=∑f(i);
ans可以合到∑当中去。
所以∑(ans*w(i))>=∑f(i);
∑(ans*w(i))-∑f(i)>=0;
∑(ans*w(i)-f(i))>=0;
所以我们建边直接建立ans*w(i)-f(i);
之后我们看看有没有环,如果存在负环也不行。
而我们要输出的ans就是需要二分了。
二分之后建图,(i,j)的值就是ans*w(i,j)-f(j);
如果有负环,说明ans小了,需要变大一点,反之变小一点。
-
#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;
-
}