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