2020.9.29排位赛第二场补题

18 篇文章 0 订阅

2017ICPC乌鲁木齐A coins

题意:给你n个正面朝下的硬币,你每次可以随意拿p个硬币,每次把这k个硬币往上抛,如此这样抛m次,问最后正面朝上的期望是多少。

思路:概率DP,设dp[i][j]为第i次抛硬币后有j个硬币朝上的概率。

一共有两种推法,
第一种:正向推:我是已经抛了i次硬币了,那么dp[i][j]是我当前j个硬币朝上的概率,我设这次抛硬币个有k个硬币朝上,那么这里会有两种情况:第一种n-j>=p,那么我这次抛硬币都选正面朝下的。
dp[i+1][j+k] += dp[i][j]*C[p][k]*1/2^p
第二种情况,即n-j小于p,那么我们不得不选正面朝上的硬币:
dp[i+1][k+n-p]+=dp[i][j]*C[p][k]*1/2^p
第二种:反向推:对于dp[i][j]我这是第i次抛硬币,前一次抛p个硬币有k个硬币朝上,那么我是从什么样的状态转移过来的?这里同样会出现两种情况:
第一种情况:n-(j-k)>= p,即上一次抛所选择的都是正面朝下的:
dp[i][j] += dp[i][j-k]*C[p][k]*1/2^p,
第二种情况,上一次上一次抛硬币时有K个硬币朝上,k>=n-p+1,那么
dp[i][j] += dp[i][k]*C[p][n-j]*1/2^p

法一:

#include<cstdio>
#include<cstring>
const int N=111;
int m,n,p;///
double c[N][N],pp[N];///组合数以及1/2的n次方
double dp[N*2][N*2];///第i次投硬币时,有j个正面朝上的概率
int main(){
    pp[0] = 1.0;
    for(int i = 1 ;  i <= N ; i++)
        pp[i] = pp[i-1]/2;
    c[1][0] = 1; c[1][1] = 1;
    for(int i = 2 ; i < N ; i++){
        c[i][0] = 1;c[i][i] = 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 <= 100 ; i++) printf("i=%d,%f\n",i,pp[i]);
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d %d %d",&n,&m,&p);
        memset(dp,0,sizeof(dp));
        dp[0][0] =1;
        for(int i = 0 ; i < m ; i++){
            for(int j = 0 ;j <= n ;j++){
                if(dp[i][j]!=0)
                for(int k = 0 ; k <= p ; k++){
                    if(n - j >= p)  dp[i+1][j+k] += dp[i][j]*c[p][k]*pp[p];
                    else dp[i+1][n-p+k] += dp[i][j]*c[p][k]*pp[p];
                }
            }
        }
        double ans = 0;
        for(int i = 1 ; i <= n ; i++)
            ans += dp[m][i]*i;
        printf("%.3f\n",ans);
    }
    return 0;
}

法二:

#include<cstdio>
#include<cstring>
const int N=111;
int m,n,p;///
double c[N][N],pp[N];///组合数以及1/2的n次方
double dp[N*2][N*2];///第i次投硬币时,有j个正面朝上的概率
int main(){
    pp[0] = 1.0;
    for(int i = 1 ;  i <= N ; i++)
        pp[i] = pp[i-1]/2;
    c[1][0] = 1; c[1][1] = 1;
    for(int i = 2 ; i < N ; i++){
        c[i][0] = 1;c[i][i] = 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 <= 100 ; i++) printf("i=%d,%f\n",i,pp[i]);
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d %d %d",&n,&m,&p);
        dp[0][0] =1;
        for(int i = 1 ; i <= n ;i++)    dp[0][i] = 0;
        for(int i = 1 ; i <= m ; i++){
            for(int j = 0 ;j <= n ;j++){
                dp[i][j] = 0;
                for(int k = 0 ; k <= p ; k++){
                    if(j < k)   break;
                    if(n - (j-k) >= p)
                        dp[i][j] += dp[i-1][j-k]*c[p][k]*pp[p];
                }
                if(n-j>=0)
                for(int k = n-p+1; k <= n ; k++)
                    dp[i][j] += dp[i-1][k]*c[p][n-j]*pp[p];
            }
        }
        double ans = 0;
        for(int i = 1 ; i <= n ; i++)
            ans += dp[m][i]*i;
        printf("%.3f\n",ans);
    }
    return 0;
}

D:公式:
圆周上n个点两两相连,最把圆分成几个部分
(n^6 - 6n^3 + 23n^2 - 18*n +24)/24

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值