Luogu P3239 [HNOI2015]亚瑟王

题目链接 \(Click\) \(Here\)

期望神题。最开始一直尝试推朴素一点的,逻辑上的\(DP\)式子,后来发现一直出锅,可能是我的式子没容斥对。。。

题解中给出的想法是这样的:

首先,如果直接一轮一轮地进行期望推导,会发现前面有冲突的情况。枚举第 \(i\)轮第 \(j\)张卡时既要保证\(i-1\)轮都没有发动过第 \(j\) 张卡,又要保证\(i\) 轮没有发动过前 \(j−1\) 张卡,再乘 \(p_i\) 算概率。但是这样怎么算都算不对,其实感觉也是一个“意识”调题的过程吧,反正最终把样例调到 \(3.21\) 左右发现概率对不上(样例解释),于是还是放弃了。

因此考虑建立无后效性的\(dp\)方程。因为需要满足 “如果发动了当前的卡”,那么就停止本轮,所以方程需要和前缀有关。令 \(f[i][j]\)表示在所有的 \(r\) 轮里,前 \(i\) 张卡有 \(j\) 个发动了的概率。此时对于任意的第 \(k\) 张卡就可以用 \(f[k-1]\) 有关的数据推出来了。

为了规避前\(i - 1\)张卡带来的影响,题解采取了非常有意思的措施:对于前\(i\)张卡片在全部\(r\)轮中的出现状况做出统计,从而不必再次考虑前面卡牌的约束。第一张卡片在\(r\)轮中均为出现的概率是\((1-p_1)^r\)。对于每个访问到的\(i\),前面\(i-1\)张的出现情况已被纳入统计,可以忽略前面的所有影响,即把其在任意\(k\)轮中未出现的概率转换为\((1 - p_i)^{k}\)(感性的说,就是前\(i-1\)张卡都不会在这\(k\)次中出现,所以第\(i\)张就是第一张,前面带来的要乘上去的概率就是\(1\))。

转换完成后接下来就是比较简单的\(DP\)推导了。具体请移步__stdcall学长的博客

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;

int T, n, r;
double d[N], p[N], fp[N], _pow[N][N], f[N][N];

void Init () {
	memset (f, 0, sizeof (f));
	memset (fp, 0, sizeof (fp));
}

int main () {
	cin >> T;
	while (T--) {
		Init ();
		cin >> n >> r;
		for (int i = 1; i <= n; ++i) {
			cin >> p[i] >> d[i];
		}
		for (int i = 1; i <= n; ++i) {
			_pow[i][0] = 1.0;
			for (int j = 1; j <= r; ++j) {
				_pow[i][j] = _pow[i][j - 1] * (1.0 - p[i]);
			}
		}
		f[1][0] = _pow[1][r], f[1][1] = fp[1] = 1 - f[1][0];
		for (int i = 2; i <= n; ++i) {
			for (int j = 0; j <= r; ++j) {
				fp[i] += f[i - 1][j] * (1 - _pow[i][r - j]);
				if (j != 0) {
					f[i][j] += f[i - 1][j - 1] * (1 - _pow[i][r - j + 1]);
				}
				f[i][j] += f[i - 1][j] * _pow[i][r - j];
			}
		}
		//_pow[i][j] = (1 - p[i]) ^ j
		double ans = 0;
		for (int i = 1; i <= n; ++i) ans += fp[i] * d[i];
		printf ("%.10lf\n", ans);
	}
}

posted @ 2019-03-11 23:03  maomao9173  阅读(125)  评论(0编辑  收藏  举报