ACM-ICPC 2017 Asia Urumqi A.Coins
题目大意
有 n n n枚硬币反面朝上,有 m m m次操作,每次需要投掷 p p p枚硬币,问你最后正面朝上的硬币个数的期望是多少
解题思路
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示第
i
i
i次投掷有
j
j
j枚硬币正面朝上的概率,
k
k
k表示第
i
i
i次投掷
p
p
p枚硬币后有
k
k
k枚硬币正面朝上的概率,那么其转移方程即为:
{
d
p
[
i
]
[
j
+
k
]
=
∑
d
p
[
i
−
1
]
[
j
]
×
C
p
k
×
2
−
p
j
≤
n
−
p
d
p
[
i
]
[
j
−
(
p
−
(
n
−
j
)
)
+
k
]
=
∑
d
p
[
i
−
1
]
[
j
]
×
C
p
k
×
2
−
p
j
>
n
−
p
\begin{cases} dp[i][j + k]=\sum dp[i-1][j] \times C_{p}^{k} \times 2^{-p}\qquad \qquad \qquad \qquad \ \ \ j \leq n-p \\ dp[i][j - (p - (n - j)) + k] = \sum dp[i-1][j] \times C_{p}^{k} \times 2^{-p} \qquad j > n - p \\ \end{cases}
{dp[i][j+k]=∑dp[i−1][j]×Cpk×2−p j≤n−pdp[i][j−(p−(n−j))+k]=∑dp[i−1][j]×Cpk×2−pj>n−p
其中第一个式子表示当前反面朝上的硬币数目多于
p
p
p枚,那么我们只要从剩余的
n
−
j
n-j
n−j枚硬币中选择
p
p
p枚反面朝上的硬币进行投掷,最后成功将
k
k
k枚硬币变成正面朝上的概率,第二个式子表示当期反面朝上的硬币数目少于
p
p
p枚,那么我们就需要选择一些已经正面朝上的硬币进行投掷,其数目为
p
−
(
n
−
j
)
p-(n-j)
p−(n−j),同样,
k
k
k表示投掷
p
p
p枚硬币后正面朝上的硬币个数
则有期望 E = ∑ i = 1 n d p [ m ] [ i ] × i E=\sum_{i=1}^{n}dp[m][i] \times i E=∑i=1ndp[m][i]×i
预处理出 100 100 100以内的 C k p C_{k}^{p} Ckp和 2 − p 2^{-p} 2−p后根据公式计算即可
注: C C C数组开 l o n g l o n g longlong longlong会爆掉,需要使用 d o u b l e double double类型存储
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
double P[maxn];
double C[maxn][maxn];
double dp[maxn][maxn];
void pre() {
P[0] = C[0][0] = 1.0;
for (int i = 1; i < maxn; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j) C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
for (int i = 1; i < maxn; ++i) P[i] = P[i - 1] / 2.0;
}
int main() {
pre();
int T;
scanf("%d", &T);
while (T--) {
int n, m, p;
scanf("%d%d%d", &n, &m, &p);
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i = 1; i <= m; ++i) {
for (int j = 0; j <= n; ++j) {
for (int k = 0; k <= p; ++k) {
if (n - j >= p) dp[i][j + k] += dp[i - 1][j] * C[p][k] * P[p];
else dp[i][j - (p - (n - j)) + k] += dp[i - 1][j] * C[p][k] * P[p];
}
}
}
double res = 0;
for (int i = 1; i <= n; ++i) res += dp[m][i] * i;
printf("%.3f\n", res);
}
return 0;
}