@codeforces - 739E@ Gosha is hunting


@description - translation@

Gosha 的目标是成为宝可梦大师。现在有 n 个宝可梦, Gosha 手里有 a 个普通球与 b 个 超级球。已知第 i 个宝可梦被普通球捕获的概率 pi 与被超级球捕获的概率 ui。Gosha 先决定哪些宝可梦扔普通球,哪些扔超级球;如果一个宝可梦同时被 Gosha 扔了两个球,则如果它被任何一个抓捕都算作被捕获,捕获后另外一个球依然消耗掉。

Gosha 想知道他在最优决策下,抓捕到的宝可梦的最大期望数量。

input
第一行包含三个整数 n a b(2 <= n <= 2000, 0 <= a, b <= n)。
第二行包含 n 个实数 p1, p2, p3, ..., pn(0 <= pi <= 1)。
第三行包含 n 个实数 u1, u2, u3, ..., un(0 <= ui <= 1)。

output
输出最大期望数量。

simple input
3 2 2
1.000 0.000 0.500
0.000 1.000 0.500

simple output
2.75

@solution - version - 1@

CF 给的正解好像是 dp 加鬼畜的优化,但是这道题也存在网络流的做法。
UPD in 2019/1/3:官方题解并不是 dp 而是贪心的样子。

考虑第 i 只宝可梦的四类决策:
(1)不扔球,对答案的贡献为 0。
(2)扔普通球,对答案的贡献为 pi。
(3)扔超级球,对答案的贡献为 ui。
(4)双球齐下,对答案的贡献为 1 - (1 - pi)(1 - ui) = pi + ui - pi*ui。

球的个数限制可以用边的容量去限制:建两个新点 P 与 U,源点向他们分别连容量为 a,b,费用为 0 的边。

然后对于每一个宝可梦建一个点,P, U 连第 i 只宝可梦容量为 1,费用为 pi, ui 的边。

宝可梦怎么向汇点连边呢?假如只运了一个流过来,对应情况(3),(4),此时产生的费用是 pi/ui ,费用不需要再变化;假如运了两个流过来,对应情况(4),此时产生的费用是 pi + ui,我们需要费用再减去 pi*ui。

有点像 “你多运输几个流过来才能解锁新的费用” 的意思【个人总结 www 很奇特的比喻】。
对于这样的题,有一个套路就是:连两条边,使得第一条比第二条优,且第一条的容量有限。

具体到这道题,我们每只宝可梦向汇点连两条边:费用为 0,容量为 1;费用为 -pi*ui,容量为 1。因为我们是求解的最大期望,所以流肯定先往费用为 0 的边跑,再往费用为 1 的边跑,这样就实现了我们的目标。

@accepted code - version - 1@

因为是求解的最大费用流,所以上文提到的所有费用对应到代码中全部取反 qwq。

#include<cstdio>
#include<queue>
#include<cmath>
using namespace std;
const int MAXN = 2000;
const int MAXV = 2*MAXN;
const int MAXE = 5*MAXN;
const int INF = (1<<30);
const double EPS = 1E-7;
bool cmp(double a, double b) {
	return a - b < -EPS;
}
bool equal(double a, double b) {
	return -EPS < a - b && a - b < EPS;
}
struct FlowGraph{
	struct edge{
		int to, cap, flow; double dis;
		edge *nxt, *rev;
	}edges[2*MAXE + 5], *cur[MAXV + 5], *adj[MAXV + 5], *ecnt;
	int S, T; double mindis, cost, dist[MAXV + 5]; bool vis[MAXV + 5], inq[MAXV + 5];
	deque<int>que;
	void init() {
		ecnt = &edges[0];
	}
	void restore() {
		for(int i=S;i<=T;i++)
			dist[i] = INF, cur[i] = adj[i];
	}
	void addedge(int u, int v, int c, double w) {
		edge *p = (++ecnt);
		p->to = v, p->cap = c, p->dis = w, p->flow = 0;
		p->nxt = adj[u], adj[u] = p;
		edge *q = (++ecnt);
		q->to = u, q->cap = 0, q->dis = -w, q->flow = 0;
		q->nxt = adj[v], adj[v] = q;
		q->rev = p, p->rev = q;
	}
	bool relabel() {
		que.push_back(S); inq[S] = true; dist[S] = 0;
		while( !que.empty() ) {
			int f = que.front(); que.pop_front();
			for(edge *p=adj[f];p!=NULL;p=p->nxt) {
				if( p->cap > p->flow && cmp(dist[f] + p->dis, dist[p->to]) ) {
					dist[p->to] = dist[f] + p->dis;
					if( !inq[p->to] ) {
						if( !que.empty() && cmp(dist[p->to], dist[que.front()])) que.push_front(p->to);
						else que.push_back(p->to);
					}
				}
			}
		}
		mindis = dist[T];
		return !(dist[T] == INF);
	}
	int aug(int x, int tot) {
		if( x == T ) {
			cost += mindis*tot;
			return tot;
		}
		int sum = 0; vis[x] = true;
		for(edge *&p=cur[x];p!=NULL;p=p->nxt) {
			if( !vis[p->to] && p->cap > p->flow && equal(dist[x] + p->dis, dist[p->to]) ) {
				int del = aug(p->to, min(p->cap-p->flow, tot-sum));
				p->flow += del, p->rev->flow -= del, sum += del;
				if( sum == tot ) break;
			}
		}
		vis[x] = false;
		return sum;
	}
	int min_cost_max_flow() {
		int flow = 0; restore();
		while( relabel() )
			flow += aug(S, INF), restore();
		return flow;
	}
}G;
double p[MAXN + 5], u[MAXN + 5];
int main() {
	int n, a, b; G.init();
	scanf("%d%d%d", &n, &a, &b);
	for(int i=1;i<=n;i++)
		scanf("%lf", &p[i]);
	for(int i=1;i<=n;i++)
		scanf("%lf", &u[i]);
	G.S = 0, G.T = 2+n+1;
	G.addedge(G.S, n+1, a, 0), G.addedge(G.S, n+2, b, 0);
	for(int i=1;i<=n;i++) {
		G.addedge(n+1, i, 1, -p[i]), G.addedge(n+2, i, 1, -u[i]);
		G.addedge(i, G.T, 1, 0), G.addedge(i, G.T, 1, p[i]*u[i]);
	}
	int ans = G.min_cost_max_flow();
	printf("%lf\n", -G.cost);
}

@solution - version - 2@

UPD in 2019/1/3
可以发现,最优情况下,我们必定将所有球用完。

所以我们将问题转换为:恰好用 a 个普通球与 b 个超级球的情况下,我们能取到的最大期望值。
满眼都是带权二分的套路感。

但是我们有两个限制个数的量怎么办?
当然是二分套二分啦!

二分使用一个球的代价,代价越大用球越少,代价越小用球越多。
上界为 1:即使抓住了一个,期望减一下就成非正数了,所以一个都不用。
下界为 0:用球不花费,随便用。
注意此题因为概率是实数,所以必须使用实数二分。

其实这道题的凸性很容易证明:因为我们要最优解,所以肯定是先扔概率大的再扔概率小的,所以最优解的增长趋势会越来越缓慢,所以就是个凸的。

@accepted - version - 2@

#include<cstdio>
const int MAXN = 2000;
double p[MAXN + 5], u[MAXN + 5];
double ans;
int n, a, b, cnt1, cnt2;
bool Check2(double x, double y) {
	cnt1 = cnt2 = ans = 0;
	for(int i=1;i<=n;i++) {
		int type = 0; double tmp = ans;
		if( ans + p[i] - x > tmp )
			tmp = ans + p[i] - x, type = 1;
		if( ans + u[i] - y > tmp )
			tmp = ans + u[i] - y, type = 2;
		if( ans + p[i] + u[i] - p[i]*u[i] - x - y > tmp )
			tmp = ans + p[i] + u[i] - p[i]*u[i] - x - y, type = 3;
		if( type & 1 ) cnt1++;
		if( type & 2 ) cnt2++;
		ans = tmp;
	}
	return cnt2 >= b;
}
bool Check1(double x) {
	double le = 0, ri = 1;
	for(int i=1;i<=50;i++) {
		double mid = (le + ri) / 2;
		if( Check2(x, mid) ) le = mid;
		else ri = mid;
	}
	Check2(x, le); ans += le*b;
	return cnt1 >= a;
}
int main() {
	scanf("%d%d%d", &n, &a, &b);
	for(int i=1;i<=n;i++)
		scanf("%lf", &p[i]);
	for(int i=1;i<=n;i++)
		scanf("%lf", &u[i]);
	double le = 0, ri = 1;
	for(int i=1;i<=50;i++) {
		double mid = (le + ri) / 2;
		if( Check1(mid) ) le = mid;
		else ri = mid;
	}
	Check1(le);
	printf("%lf\n", ans + le*a);
}

@details@

本题听机房里的人说,不用 EPS 过不了,EPS 取太大也过不了。
诶嘿嘿谢谢了,起码我不用调试那么久了 qwq。

UPD in 2019/1/3:这么多非官方的正解都能过……
甚至还比官方优秀 2333。

posted @ 2018-12-25 19:39  Tiw_Air_OAO  阅读(318)  评论(0编辑  收藏  举报