【CodeForces】【網絡流】【帶權二分】739E-Gosha is hunting

CodeForces 739E Gosha is hunting

題目大意

NN個精靈,Gosha 手中有AA個普通球和BB個超級球,已知第ii個精靈被普通球捕獲的概率爲pip_i,被超級球捕獲的概率爲uiu_i。Gosha 可以先決定對哪些精靈扔普通球,哪些扔超級球。

Gosha 想知道在最優決策下,捕獲的精靈的最大期望數量。

注意可以向一個精靈扔最多一個普通球和超級球。這時這個精靈被這些球中的任何一個球捕獲到都算作被捕獲,但其他的球仍然被消耗掉。

分析

考慮對一個精靈的四種扔球情況的期望:

  1. 不扔球:捕獲概率和期望都是00
  2. 扔一個普通球:捕獲概率爲pip_i,對答案貢獻1×pi=pi1\times p_i=p_i
  3. 扔一個超級球:捕獲概率爲uiu_i,對答案貢獻爲1×ui=ui1\times u_i=u_i
  4. 扔一個超級球和一個普通球:捕獲概率爲1(1pi)(1ui)=pi+uipiui1-(1-p_i)(1-u_i)=p_i+u_i-p_iu_i,對答案貢獻爲pi+uipiuip_i+u_i-p_iu_i

我們很自然地想到兩種做法:

網絡流

我們用邊的容量去限制球的使用個數,用費用去計算貢獻。

球的總個數限制可以用邊的容量去限制:建立兩個節點P,UP,U,分別向源點連一條容量爲A,BA,B,費用爲00的邊。

那麼對於第ii個精靈,我們從PP連一條到第ii個點,容量爲11,費用爲pip_i的邊,從UU連一條到第ii個點,容量爲11,費用爲uiu_i的邊。

那麼精靈怎麼向匯點連邊?

考慮到若過來的流量只有11,對於情況2,3,顯然貢獻爲pip_i或者uiu_i,這是沒問題的。

但過來的流量是22呢?我們需要減掉pi×uip_i\times u_i

換句話說我們需要更大的流量才能夠獲得piui-p_iu_i的貢獻。

一種有效的方法是將一條邊拆成兩條:一條費用爲00,容量爲11的邊和一條費用爲piui-p_iu_i,容量爲11的邊。

由於費用流是按照最短路來增廣的,所以一定會先去那條費用爲00的邊,再去費用爲11的邊。故這樣建圖沒有問題。

注意我們要求的是最大費用最大流,所以爲了將這個問題變成最小費用最大流,代碼中所有的邊權都取反了。

帶權二分

考慮用 DP 來解決這道題:

定義狀態f(i,j,k)f(i,j,k)爲對前ii個精靈用了jj個普通球和kk個精靈球的最大期望。

轉移就按照上面講的四種情況來就是了。最終答案就是f(N,A,B)f(N,A,B)了。

換句話說我們最大的答案一定是將A,BA,B用完了的。

似乎有點像帶權二分。

由於我們需要最大化答案,所以我們一定是先扔期望大的再扔期望小的,所以最優解的增長趨勢一定會越來越慢,所以這個函數就是個凸的。 (手動腦補三維空間上的函數圖像。。。)

於是我們就可以愉快地帶權二分套帶權二分了QAQ。。。

參考代碼

備註: 這題卡精度,不用 EPS 和 EPS 太小都過不去 QAQ。。。

經測試10710^{-7}當做 EPS 是一個不錯的選擇。

寫二分時不想卡精度就直接用循環次數好了。。。

網絡流

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int Maxn = 2000;
const int Maxm = 1e4;
const int INF = 0x3f3f3f3f;
const double INFDB = (1 << 30);
const double EPS = 1e-7;

inline bool cmp(double lhs, double rhs) {
	return lhs - rhs < -EPS;
}
inline bool equal(double lhs, double rhs) {
	return -EPS < lhs - rhs && lhs - rhs < EPS;
}

struct MaxCostMaxFlow {
	struct Edge {
		int to, cap;
		double cos;
		Edge *nxt, *bak;
	};
	Edge pool[Maxm * 2 + 5];
	Edge *ecnt, *G[Maxn + 5];
	inline void addedge(int u, int v, int cap, double c) {
		Edge *p = ++ecnt;
		p->to = v, p->cap = cap, p->cos = c;
		p->nxt = G[u], G[u] = p;
		p->bak = p + 1;
		p = ++ecnt;
		p->to = u, p->cap = 0, p->cos = -c;
		p->nxt = G[v], G[v] = p;
		p->bak = p - 1;
	}
	int node, s, t;
	double d[Maxn + 5];
	Edge *cpy[Maxn + 5];
	double cost;
	void init(int n) {
		ecnt = &pool[0];
		node = n;
		for(int i = 1; i <= n; i++)
			G[i] = NULL;
	}
	bool vis[Maxn + 5];
	int dfs(int u, int tot) {
		if(u == t) return tot;
		vis[u] = true;
		int sum = 0;
		for(;cpy[u] != NULL; cpy[u] = cpy[u]->nxt) {
			int v = cpy[u]->to;
			double w = cpy[u]->cos;
			if(!vis[v] && equal(d[v], d[u] + w) && cpy[u]->cap > 0) {
				int delta = dfs(v, min(tot - sum, cpy[u]->cap));
				cpy[u]->cap -= delta, cpy[u]->bak->cap += delta;
				cost += cpy[u]->cos * delta;
				sum += delta;
				if(sum == tot) break;
			}
		}
		vis[u] = false;
		return sum;
	}
	bool spfa(int _s, int _t) {
		static bool inq[Maxn + 5];
		memset(inq, false, sizeof inq);
		for(int i = 1 ; i <= node; i++)
			d[i] = INFDB, cpy[i] = G[i];
		queue<int> q;
		q.push(_s), d[_s] = 0, inq[_s] = true;
		while(!q.empty()) {
			int u = q.front();
			q.pop(), inq[u] = false;
			for(Edge *p = G[u]; p != NULL; p = p->nxt) {
				int v = p->to;
				if(p->cap > 0 && cmp(d[u] + p->cos, d[v])) {
					d[v] = d[u] + p->cos;
					if(!inq[v]) {
						inq[v] = true;
						q.push(v);
					}
				}
			}
		}
		return d[_t] != INFDB;
	}
	double mcmf(int _s, int _t) {
		s = _s, t = _t, cost = 0;
		while(spfa(s, t)) {
			memset(vis, false, sizeof vis);
			dfs(s, INF);
		}
		return cost;
	}
};

int N, A, B;
double P[Maxn + 5], U[Maxn + 5];
MaxCostMaxFlow f;

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	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]);
	int s = N + 1, t = N + 2, p = N + 3, u = N + 4;
	f.init(N + 4);
	f.addedge(s, p, A, 0), f.addedge(s, u, B, 0);
	for(int i = 1; i <= N; i++) {
		f.addedge(p, i, 1, -P[i]), f.addedge(u, i, 1, -U[i]);
		f.addedge(i, t, 1, 0), f.addedge(i, t, 1, P[i] * U[i]);
	}
	printf("%lf\n", -f.mcmf(s, t));
	return 0;
}

帶權二分

#include <cstdio>
#include <algorithm>
using namespace std;

const int Maxn = 2000;

int N, A, B;
double P[Maxn + 5], U[Maxn + 5];

double f[Maxn + 5];
int cntp[Maxn + 5], cntu[Maxn + 5];
bool check(double p, double u, int typ) {
	for(int i = 1; i <= N; i++) {
		f[i] = f[i - 1], cntp[i] = cntp[i - 1], cntu[i] = cntu[i - 1];
		if(f[i - 1] + P[i] - p > f[i])
			f[i] = f[i - 1] + P[i] - p, cntp[i] = cntp[i - 1] + 1,
			cntu[i] = cntu[i - 1];
		if(f[i - 1] + U[i] - u > f[i])
			f[i] = f[i - 1] + U[i] - u, cntu[i] = cntu[i - 1] + 1,
			cntp[i] = cntp[i - 1];
		if(f[i - 1] + U[i] + P[i] - U[i] * P[i] - p - u > f[i])
			f[i] = f[i - 1] + U[i] + P[i] - U[i] * P[i] - p - u,
			cntu[i] = cntu[i - 1] + 1, cntp[i] = cntp[i - 1] + 1;
	}
	return typ == 0 ? cntu[N] > B : cntp[N] > A;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	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 lb = 0, ub = 1, lb1, ub1;
	for(int i = 1; i <= 50; i++) {
		double mid = (lb + ub) / 2;
		lb1 = 0, ub1 = 1;
		for(int i = 1; i <= 50; i++) {
			double mid1 = (lb1 + ub1) / 2;
			if(check(mid, mid1, 0))
				lb1 = mid1;
			else ub1 = mid1;
		}
		if(check(mid, ub1, 1)) lb = mid;
		else ub = mid;
	}
	check(ub, ub1, 0);
	printf("%lf\n", f[N] + A * ub + B * ub1);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章