计蒜客 2017ICPC乌鲁木齐A Coins(概率期望dp)

大致题意:总共有n个硬币,初始的时候所有的都朝下。然后进行m次投币,每次投币要投p个硬币,这p个硬币相互独立,且最后朝向正反概率相同。为了使得最后朝上的硬币最多,采取最优的投币方式。现在问你按照这种方式,最后朝上的硬币个数的期望是多少。

对于这种题目,如果不能够推出一个O(1)的通项公式,那么基本就是一个概率期望dp了。对于这题,我们首先考虑怎样才是最优的投币方式,概率我们不能决定,但是我们可以选择每次投哪p个硬币。显然,每次我们投的时候都只投那些朝下的硬币,不动那些已经朝上的硬币,只有当朝下的硬币不够k个的时候,我们才考虑去动那些已经朝上的硬币。然后,我们来考虑在不同情况下的转移。

令dp[i][j]表示进行到第i次投币,已经有j个朝上的概率。分两种情况,当朝下的硬币大于p个,那么只需要动朝下的硬币,那么有转移方程:dp[i][j]=\sum_{k=j-p}^j dp[i-1][k]*C_{p}^{j-k}*2^p,表示上一轮有k个朝上,到了当前这一轮投了p个,其中有j-k个朝上。然后当剩下的朝下的硬币小于p时,又可以分为两部分,一是朝上的币数目比上一轮增加,二是比上一轮减少,因为会有一些原本朝上的硬币又要参与投币,但最后发现可以统一成一个转移方程:dp[i][j]=\sum_{k=j-p}^{j+p}dp[i-1][k]*C_{p}^{n-j}*2^p。具体见代码:

#include<bits/stdc++.h>
#define mod 998244353
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define LL long long
#define N 110

using namespace std;

double dp[N][N],c[N][N],pw[N];
int n,m,p;

void init()
{
    pw[0]=c[0][0]=1;
    for(int i=1;i<N;i++)
    {
        pw[i]=pw[i-1]/2;
        c[i][0]=1;
    }
    for(int i=1;i<N;i++)
        for(int j=1;j<=i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
}

int main()
{
    int T;
    IO; cin>>T;
    init();
    while(T--)
    {
        cin>>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=max(0,j-p);k<=j+p&&k<=n;k++)
                    if (n-k>=p&&k<=j) dp[i][j]+=dp[i-1][k]*c[p][j-k]*pw[p];
                                 else dp[i][j]+=dp[i-1][k]*c[p][n-j]*pw[p];

        double ans=0;
        for(int i=1;i<=n;i++)
            ans+=dp[m][i]*i;
        cout<<fixed<<setprecision(3)<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/81281047