• @codeforces



    @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
    ", -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
    ", ans + le*a);
    }
    

    @details@

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

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

  • 相关阅读:
    dubbo支持哪些通信协议?支持哪些序列化协议?
    spring常见面试题
    100道Java基础面试题收集整理(附答案)
    阿里面试题
    说一下的dubbo的工作原理?注册中心挂了可以继续通信吗?说说一次rpc请求的流程?
    为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗?
    layui增加转圈效果
    js防止重复提交代码
    工作流表介绍
    权限树的制作(menu)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10176027.html
Copyright © 2020-2023  润新知