【GDKOI2016】寻宝

题目大意:

有一个n个点m条边的图,每个点有两个值u,v.
选出一个闭合子图,使 uivi 最小。

1<=n<=1000,1<=m<=10000

题解:

二分个答案ans,问题转换为判定性问题:
uivi<=ans
ui<=ansvi
viansui>=0

那么每个点i的点权就是 viansui .

那我们就跑一下最大权闭合子图,判断一下是否大于0。

注意这里一定要是大于0,因为子图可以是空集,所以最大权闭合子图的权一定大于等于0。
当所有的点都是负的时,网路流中我就可以不选,那么最大权为0。
但是我必须要选一个宝藏,所以这里不能等于0。

本来这个模板题很快就可以结束的了可是我就是因为精度挂了。

以前我是这么打这种带有小数的二分的:

设wu = 我需要的精度。

int ans;
for(double l = 0, r = 1e9; l <= r; ){
    double m = (l + r) / 2;
    if(pd(m)) ans = m, r = m - wu; else l = m + wu;
}

这样打的好处是在wu = 1,即二分整数时,我不需要微调,ans就是答案。

在小数的时候,从理论上来说,是不会有问题的,但是这题坑爹的地方在于因为精度问题,我的pd求出来不一定满足单调,这就会一出偏差就上天了。

所以,在小数二分的时候请改成以下的形式:

double l = 0,  r = 1e9;
while(l < r){
    double m = (l + r) / 2;
    if(pd(m)) r = m; else l = m;
}

这样精度会好很多很多,由于是小数不用微调,具体的我也说不清,总之就得这么打。

Code:

#include
#include
#define ld long double
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define abs(a) ((a) > 0 ? (a) : -(a))
using namespace std;

const ld inf = 1e9;

const int N = 100005;

int n, x, y, u[N], v[N];

int b[N][2], te;

int next[N], to[N], final[N], tot = 1; ld w[N];

void link(int x, int y, ld z) {
    next[++ tot] = final[x], to[tot] = y, w[tot] = z, final[x] = tot;
    next[++ tot] = final[y], to[tot] = x, w[tot] = 0, final[y] = tot;
}

int S, T, cur[N], co[N], d[N];

ld dg(int x, ld flow) {
    if(x == T) return flow;
    ld use = 0;
    for(int i = cur[x]; i; i = next[i], cur[x] = i)  {
        int y = to[i];
        if(w[i] > 1e-8 && d[y] + 1 == d[x]) {
            ld tmp = dg(y, min(flow - use, w[i]));
            use += tmp; w[i] -= tmp; w[i ^ 1] += tmp;
            if(flow - use < 1e-8) return use;
        }
    }
    cur[x] = final[x];
    if(!(-- co[d[x]])) d[S] = T;
    ++ co[++ d[x]];
    return use;
}

bool pd(ld m) {
    fo(i, 1, tot) next[i] = 0;
    fo(i, 1, T) final[i] = 0;
    tot = 1;
    memset(d, 0, sizeof d);
    memset(co, 0, sizeof co);
    memset(cur, 0, sizeof cur);
    fo(i, 1, te) link(b[i][0], b[i][1], inf);
    ld s = 0;
    fo(i, 1, n) {
        ld d = m * v[i] - u[i];
        if(d > 0) link(S, i, d), s += d; else link(i, T, -d);
    }
    co[0] = T;
    for(; d[S] < T;) s -= dg(S, inf);
    return s > 0;
}


int main() {
    scanf("%d", &n);
    fo(i, 1, n) for(scanf("%d", &x); x; x --) b[++ te][0] = i, scanf("%d", &b[te][1]);
    fo(i, 1, n) scanf("%d %d", &v[i], &u[i]);
    S = n + 1, T = S + 1;
    ld l = 0, r = 1e4;
    while(r - l >= 1e-8) {
        ld m = (l + r) / 2;
        if(pd(m)) r = m; else l = m;
    }
    if(abs(l) < 1e-8) printf("CanNotFindTreasure!\n"); else printf("%.10Lf\n", l);
}

你可能感兴趣的:(网络流)