题意:
给定n个硬币,初始美国都是方面超上,m次机会,每次必须选k翻转,问采取最优策略的翻转方式,得到的正面朝上的期望是多少
思路:
这题按说需要高精度,但是懒得写,用double过了,可能是因为保留的结果的位数较小,不会误差那么大
首先,最优策略就是:每次选取k个的时候,我们先选反面朝上的硬币,如果不够,再选正面朝上的,
因为每次的翻转只跟上次的翻转情况有关,所以我们可以推导一下递推式dp,
其实很容易想到dp[i][j] 表示 翻转完第i次时,j个硬币正面朝上的概率;
如果不知道,可以这样考虑:首先dp得有一维表示翻转次数,用来递推相邻两次翻转关系;其次每次翻转影响的是正面朝上的硬币的个数(因为我们要求的就是这个,所以不用反面朝上的个数),所以有一维需要表示正面朝上的个数;最后还要表示当前状态的概率,那就是直接用dp[][] 表示了,即可以得到dp[i][j];
递推式:我们得到了dp[i][j] 第 i 次翻转后有 j 个正面朝上,这时候我们进行第 i+1 次翻转,会遇到两种情况:
1. j+k<=n: 即我们可以选k个反面朝上的硬币操作,然后可以求得这k个硬币中朝上的个数 t 的概率p,那么dp[i+1][j+t] = dp[i][j]*p; p = C(k,t)* (1/2)^k
2. j+k > n: 即需要选择tt {=(j+k-n)} 个正面朝上的硬币进行翻转,所以第i+1次翻转后确定的正面朝上的个数为j-tt个 ,然后考虑k个刚翻转的正面朝上的个数t 的概率p,p还是C(k,t)* (1/2)^k;所以此时递推式为 dp[i+1][j-tt+t] = dp[i][j]*p;
可以发现:对于给定的k,t 的概率p恒为 C(k,t)* (1/2)^k,所以我们可以先预处理这一部分;
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 100 + 7;
int n, m, k;
double dp[maxn][maxn];
double c[maxn][maxn];
void init() {
double t1 = 1;
for(int i = 1; i <= k; ++i) t1 *= 2.0;
c[k][0] = 1;
for(int i = 1; i <= k; ++i) {
c[k][i] = c[k][i-1]*(k-i+1)/i;
}
for(int i = 0; i <= k; ++i) {
c[k][i] /= t1;
}
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d%d", &n, &m, &k);
init();
memset(dp, 0, sizeof dp);
dp[0][0] = 1.0;
for(int i = 0; i < m; ++i) {
for(int j = 0; j <= n; ++j) {
if(j + k <= n) {
for(int t = 0; t <= k; ++t) {
dp[i+1][j+t] += dp[i][j]*c[k][t];
}
} else {
int tt = j+k-n;
for(int t = 0; t <= k; ++t) {
dp[i+1][j+t-tt] += dp[i][j]*c[k][t];
}
}
}
}
double ans = 0;
for(int i = 0; i <= n; ++i) {
ans += (i*dp[m][i]);
}
printf("%.3f\n", ans);
}
return 0;
}