题目链接:https://nanti.jisuanke.com/t/28960
题目大意:有n个面朝下的硬币,扔m次,每次取k个,希望面朝上尽可能多,问最后面朝上个数的期望
题目思路:设一个dp[i][j],表示第i次扔有j个面朝上的概率。所以我们可以发现一个递推式,当j+k<=n的时候,就说明现在还有k个硬币是面朝下的,为了朝上的尽可能多,肯定是从这里面挑来扔。然后用l来表示这k个中有l个正面朝上,那么dp[i+1][j+l]表示扔i+1次后多了l个正面朝上硬币的概率。这个概率也就是k个里面有l个正面朝上的概率C(k,l)/(2^k)再乘以dp[i][j]。如果说j+k>n,那么就只能从正面的里面挑几个也拿出来扔。很明显现在还有n-j个面朝下的,那么这些都是可以拿来扔的,那就还需要k-(n-j)个面朝上的硬币,那我们的转移方程的左边就变成了dp[i+1][j-(k-n-j))+l]了。有一个注意点,那就是组合数到了33的时候就可以爆ll,所以这里需要用double和杨辉三角的原理来组合数,因为需要准确的数字不能取模。
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define MAXN 105
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
double c[MAXN][MAXN];
double dp[MAXN][MAXN];
void init(){
rep(i,1,100){
c[i][0]=c[i][i]=1;
}
rep(i,2,100){
rep(j,1,i-1){
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
}
int main()
{
int t,n,m,k;
scanf("%d",&t);
init();
while(t--){
scanf("%d%d%d",&n,&m,&k);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
double x=pow(2,-k);
rep(i,0,m-1){
rep(j,0,n){
rep(l,0,k){
if(j+k<=n){
dp[i+1][j+l]+=dp[i][j]*c[k][l]*x;
}
else{
dp[i+1][j-(k-(n-j))+l]+=dp[i][j]*c[k][l]*x;
}
}
}
}
double ans=0;
rep(i,1,n){
ans+=dp[m][i]*i;
}
printf("%.3lf\n",ans);
}
return 0;
}