如果能够求出每张卡牌在所有
r
轮中被发动的概率
∑ni=1g[i]d[i]
第一步推出,
g[1]=1−(1−p[1])r
。
再考虑第二张:
情况一:如果第
1
张牌没有发动过技能,那么第
情况二:如果第
1
张牌发动过
结合这个例子,可以得到,对于任意的
i>1
,在第
1
张牌到第
根据这个性质,就能想到一个DP模型:
f[i][j]
表示前
i
张牌中,恰好有
转移就比较好想了。分第
1:发动。那么前
i−1
张牌一定有
j−1
张牌被发动技能,因此对于第
i
张牌,在
f[i][j]+=(1−(1−p[i])r−j+1)f[i−1][j−1]
2:不发动。那么前
i−1
张牌中一定有
j
张牌被发动技能,因此对于第
f[i][j]+=(1−p[i])r−jf[i−1][j]
因此,完整的转移方程为:
f[i][j]=((1−(1−p[i])r−j+1)f[i−1][j−1])[j>0]
+((1−p[i])r−jf[i−1][j])[i≠j]
那么求
g
就更容易了:
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 234, R = 143;
int n, r, d[N];
double p[N], f[N][R], g[N], pw[N][R];
void work() {
memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g));
int i, j; scanf("%d%d", &n, &r);
for (i = 1; i <= n; i++) scanf("%lf%d", &p[i], &d[i]), pw[i][0] = 1;
if (!r) return (void) puts("0.0000000000");
for (i = 1; i <= n; i++) for (j = 1; j <= r; j++)
pw[i][j] = pw[i][j - 1] * (1.0 - p[i]);
f[1][0] = pw[1][r]; f[1][1] = g[1] = 1.0 - pw[1][r];
for (i = 2; i <= n; i++) for (j = 0; j <= min(i, r); j++) {
if (j) f[i][j] += f[i - 1][j - 1] * (1.0 - pw[i][r - j + 1]);
if (i != j) f[i][j] += f[i - 1][j] * pw[i][r - j];
}
for (i = 2; i <= n; i++) for (j = 0; j <= min(i - 1, r); j++)
g[i] += f[i - 1][j] * (1.0 - pw[i][r - j]);
double ans = 0;
for (i = 1; i <= n; i++) ans += g[i] * d[i];
printf("%.10lf\n", ans);
}
int main() {
int T; cin >> T; while (T--) work();
return 0;
}