Codeforce 739 E.Gosha is hunting(費用流 | 期望dp + wqs二分套wqs二分優化)

在這裏插入圖片描述


題目大意:有 n 個神奇寶貝,有 aa 個普通球和 bb 個超級球,對沒只神奇寶貝,使用普通球有 p[i]p[i] 的概率抓獲,使用超級球有 q[i]q[i] 的概率抓獲,普通球和超級球對每個神奇寶貝只能用一次,可以即使用普通球又使用超級球,求抓獲的神奇寶貝數量的最大期望值。


費用流做法:
對第 i 只神奇寶貝使用一個普通球對答案的貢獻爲 p[i]p[i],使用超級球的貢獻爲 q[i]q[i],都使用的貢獻爲 p[i]+q[i]p[i]q[i]p[i] + q[i] - p[i] * q[i]

爲兩種球建立兩個源點 s1,s2, s1 對每個神奇寶貝連一個容量爲 1,費用爲 p[i]p[i] 的邊, s2 對每個神奇寶貝連一個容量爲 1,費用爲 q[i]q[i] 的邊,如果對一個神奇寶貝用了兩種球,貢獻還要扣掉 p[i]q[i]p[i] * q[i],因此不能直接對第 ii 只神奇寶貝建 一條到 t,容量爲 2,費用爲0的邊。

考慮減掉這一部分的貢獻,每個神奇寶貝,建兩條到 tt 的邊,容量均爲 1,其中一條費用爲0,另一條費用爲 p[i]q[i]-p[i] * q[i],如果對某個神奇寶貝只用了一種球,走的必然是 費用爲 0的邊,用了兩種球的話一定會走費用 p[i]q[i]-p[i] * q[i] 的邊。


wqs二分做法:
dp[i][j][k]表示前 i 只神奇寶貝,使用 a 個普通球,b 個超級球的最大捕捉期望,容易得到 n3n^3 的樸素 dpdp 做法,對每隻神奇寶貝考慮使用球的組合進行轉移。

假如超級球的使用沒有限制,最優答案肯定是每隻神奇寶貝都使用超級球,設使用的超級球的個數爲xx,對應情況的最優答案爲 g(x)g(x)通過打表 容易發現 x,g(x)x,g(x) 的增長幅度越來越小,也就是 x,g(x)x,g(x) 是一個上凸的函數,可以二分一個代價 cc,每用一個超級球,貢獻扣掉代價 cc,顯然 cc 的取值爲 [0,1],cc 越大,最優解使用的超級球越少,否則使用的超級球越多。

既然超級球可以二分,那麼普通球是不是也能同時二分,答案是顯然的,如果超級球剛好使用 bb 個,二分代價 c1c1 表示使用普通球的代價,單調性容易證明。在二分 c1c1 後,二分超級球的使用 c2c2,證明在普通球使用代價爲 c1c1 的情況下,超級球的使用代價越高,最優解使用超級球的數量越少,即具有單調性。

設某次得到的最優解使用的普通球和超級球的數量分別爲 x1,y1x_1,y_1, 增長使用超級球的代價 c2c2,反證:設 此時使用超級球的數量反而增多,普通球使用的數量爲 x2x_2,超級球使用的數量爲 y2y_2,滿足 y2>y1y_2 > y_1

無論 x1x2x_1 和 x_2 的關係如何,在增長超級球的代價 c2c2 前,令 y1=y2y_1 = y_2,答案都會更優,即前面一次得到的不是最優解,與假設矛盾。因此可以同時二分普通球和超級球的使用代價,嵌套二分兩個代價同時與最優解的使用數量具有單調性。

兩層二分分別二分兩種球的使用代價,轉移時枚舉對第 i 只神奇寶貝使用哪種組合進行轉移:使用普通球,使用超級球,兩者都用。

複雜度爲 O(nlog2n)O(n \log^2n),但代碼有點難寫,一般來說可能二分不到一個解使得它的使用數量恰好 = kk,因此要讓每個解的使用數量儘可能多,但這題是雙關鍵字。參考了網上直接 dpdp ,沒有讓每個解的使用次數儘可能多,稍微改動一點就會 wa。


代碼:

#include<iostream>
#include<string.h>
#include<queue>
using namespace std;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
struct ss{
	int v,c,nxt;
	double w;
}edg[maxn];
int head[maxn],cnt,vis[maxn],pre[maxn];
double d[maxn];
void init() {
	cnt = 0;
	memset(head,-1,sizeof head);
}
void add(int u,int v,int c,double w) {
	edg[cnt].v = v;
	edg[cnt].c = c;
	edg[cnt].w = w;
	edg[cnt].nxt = head[u];
	head[u] = cnt++;
	
	edg[cnt].v = u;
	edg[cnt].c = 0;
	edg[cnt].w = -w;
	edg[cnt].nxt = head[v];
	head[v] = cnt++;
}
bool spfa(int s,int t) {
	queue<int> q;
	memset(vis,0,sizeof vis);
	memset(pre,-1,sizeof pre);
	for (int i = 0; i <= t; i++)
		d[i] = -1e12;
	q.push(s);
	d[s] = 0; vis[s] = 1;
	while(!q.empty()) {
		int top = q.front();
		q.pop();
		vis[top] = 0;
		for(int i = head[top]; i + 1; i = edg[i].nxt) {
			int v = edg[i].v, c = edg[i].c; double w = edg[i].w;
			if(c && d[v] < d[top] + w - eps) {
				d[v] = d[top] + w; pre[v] = i;
				if(!vis[v]) {
					q.push(v);
					vis[v] = 1;
				}
			}
		}
	}
	return pre[t] != -1;
}
int maxflow(int s,int t,double &cost) {
	int ans = 0;
	cost = 0;
	while(spfa(s,t)) {
		int mx = inf;
		for(int i = pre[t]; i != -1; i = pre[edg[i ^ 1].v]) {
			mx = min(mx,edg[i].c);
		}
		for(int i = pre[t]; i != -1; i = pre[edg[i ^ 1].v]) {
			edg[i].c -= mx;
			edg[i ^ 1].c += mx;
			cost += mx * edg[i].w;
		}
		ans += mx;
	}	
	return ans;
}
int n,a,b;
double p[maxn],q[maxn];
int main() {
	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",&q[i]);
	}
	int s = 0, s1 = 2 * n + 1, s2 = 2 * n + 2, t = 2 * n + 3;
	add(s,s1,a,0); add(s,s2,b,0);
	for (int i = 1; i <= n; i++) {
		add(s1,i,1,p[i]);
		add(s2,i,1,q[i]);
		add(i,t,1,0);
		add(i,t,1,-p[i] * q[i]);
	}	
	double cost; int f;
	f = maxflow(s,t,cost);
	printf("%lf\n",cost);
	return 0;
}

wqs二分套wqs二分 代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 10;
const double eps = 1e-8;
int n,a,b,ta[maxn],tb[maxn];
double p[maxn],q[maxn],dp[maxn],ans;
int calc(double ca,double cb,bool f) {				//第二層二分,二分使用超級球的費用 
	for (int i = 1; i <= n; i++) {
		dp[i] = dp[i - 1]; ta[i] = ta[i - 1]; tb[i] = tb[i - 1];
		if (dp[i - 1] + p[i] - ca - dp[i] > eps) {
			dp[i] = dp[i - 1] + p[i] - ca;
			ta[i] = ta[i - 1] + 1;	
			tb[i] = tb[i - 1];
		}
		if (dp[i - 1] + q[i] - cb - dp[i] > eps) {
			dp[i] = dp[i - 1] + q[i] - cb;
			ta[i] = ta[i - 1];
			tb[i] = tb[i - 1] + 1;
		}
		if (dp[i - 1] + q[i] + p[i] - q[i] * p[i] - ca - cb - dp[i] > eps) {
			dp[i] = dp[i - 1] + q[i] + p[i] - q[i] * p[i] - ca - cb;
			ta[i] = ta[i - 1] + 1;
			tb[i] = tb[i - 1] + 1;
		}
	}
	return f ? ta[n] : tb[n];
}
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",&q[i]);	
	double l1 = 0, r1 = 1.0 ,l2 = 0, r2 = 0;
	while (r1 - l1 > eps) {
		double c1 = (l1 + r1) / 2;
		l2 = 0, r2 = 1.0;
		while (r2 - l2 > eps) {
			double c2 = (l2 + r2) / 2;
			if (calc(c1,c2,0) <= b)	r2 = c2;
			else l2 = c2;
		}
		if (calc(c1,r2,1) <= a) r1 = c1;
		else l1 = c1;
	}
	calc(r1,r2,0);
	printf("%lf\n",dp[n] + r1 * a + r2 * b);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章