[HNOI2015]亚瑟王

(看到题解里面有一篇和我最初想法挺像但好像不是大众解法的题解,有点激动,干脆自己也来写篇题解好了)

Description

你有 \(n\) 张卡片,每张有一组数据 \(d_i,\ p_i\) 表示贡献和发动概率,现在你要玩 \(r\) 轮游戏,每轮编号从 \(1\) 开始:

  • 如果这张卡片在之前被发动过,直接跳过;

  • 如果当前卡片发动了,产生 \(d_i\) 的贡献并直接开始下一轮;

  • 否则没有发动的话,换到下一张卡片。

求期望获得贡献。

多测。

\(T \leq 444,\ n \leq 220,\ r \leq 132,\ p_i \in [0,\ 1],\ d_i \in [0,\ 10 ^ 3]\)

Analysis

注意到题目暗含的意思应该是玩多少轮游戏,就会发动多少次卡片。(具体哪张并不清楚)

根据期望的线性性质,我们可以单独考虑每个点发动的概率 \(g_i\) ,那么总贡献就是 \(\sum g_i \cdot d_i\)

所以在可以不考虑第几轮的情况下,我们可以这样设一个 DP 状态: \(dp_{i,\ j}\) 表示前 \(i\) 张卡片剩下 \(j\) 次没发动的期望值。

Solution

因为每张卡片发动一次之后就不能再发动了,发动之后的卡片视作概率为 \(1\) ,贡献为 \(0\) 的“虚卡片”。

假设第 \(j\) 轮发动:

  • 对于之后的轮次,概率为 \(1\) ,可以拆成发动 \(+\) 不发动两种情况;

  • 而对于前面的轮次,概率一致,属于同类项。

如此来看的话原先定义的 \(g_i\) 实际上可以变成另一种定义:\(i\) 张卡片至少发动一次的概率

会不会算重呢?假如如果第 \(j\) 轮前面还有发动的轮次 \(k\) ,这种情况就换合并到第一次在第 \(k\) 轮发动的,前面有多个同理,不会的。

那就有 \(g_1 = 1 - (1 - p_1) ^ r\) ,如果另加前 \(j\) 轮就把 \(r\) 改成 \(j\) 就好了。

问题解决了一半。

还有一个问题,我们还是被每轮只能发动一次这个条件限制的死死的:因为前者的发动概率会直接影响后面的卡片发动概率,所以同时要算当前状态的概率以及期望。

我们就可以理解为每张卡片在它后面的每张卡片上叠了 buff 。

(不知道怎么的)猛然间想起期望入门的时候做过的这道题

前者影响后者,那就正难则反!!全部逆推,反正后面的卡片都要被前面影响,该叠 buff 就正常叠就行了。

所以就有很自然的 DP 方程式啦(注意是逆推啦):

\[dp_{i,\ j} = dp_{i + 1,\ j} \cdot (1 - p_i) ^ j + dp_{i + 1,\ j - 1} \cdot (1 - (1 - p_i) ^ j) \]

分别的两个系数就是上文讲的发动或者不发动。

(常熟还小/hanx)

Code

Code

/*

*/
#include 
using namespace std;
typedef long long ll;
const int N = 222;
int n, r, d[N];
double p[N], f[N][N];
inline int read() {
	char ch = getchar();
	int s = 0, w = 1;
	while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
	while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
	return s * w;
}
inline double dead() {
	char ch = getchar();
	ll s = 0, w = 1, k = 0;
	double m = 1; bool is = 0;
	while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
	while (isdigit(ch) || ch == '.') {
		if (ch == '.') is = 1;
		else if (!is) s = (s << 3) + (s << 1) + (ch ^ 48);
		else k = (k << 3) + (k << 1) + (ch ^ 48), m *= 0.1;
		ch = getchar();
	}
	return (m * k + s) * w;
}
inline void mian() {
	memset(f, 0, sizeof(f));
	n = read(); r = read();
	for (int i = 1; i <= n; ++i) p[i] = dead(), d[i] = read();
	for (int i = n; i >= 1; --i) {
		double P = 1.0 - p[i];
		for (int j = 1; j <= r; ++j) {
			f[i][j] = f[i + 1][j] * P + (f[i + 1][j - 1] + d[i]) * (1.0 - P);
			P *= 1.0 - p[i];
		}
	}
	printf("%.9lf\n", f[1][r]);
}
int main() {
	int T = read();
	while (T--) mian();
	return 0;
}

posted @ 2022-05-23 11:14  Illusory_dimes  阅读(27)  评论(0编辑  收藏  举报