有一个n个点m条边的图,每个点有两个值u,v.
选出一个闭合子图,使 ∑ui∑vi 最小。
1<=n<=1000,1<=m<=10000
二分个答案ans,问题转换为判定性问题:
∑ui∑vi<=ans
∑ui<=ans∗∑vi
∑vi∗ans−ui>=0
那么每个点i的点权就是 vi∗ans−ui .
那我们就跑一下最大权闭合子图,判断一下是否大于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);
}