CSP202109-4 收集卡牌

19 篇文章 0 订阅
12 篇文章 0 订阅

题目描述

小林在玩一个抽卡游戏,其中有 n 种不同的卡牌,编号为 1 到 n。每一次抽卡,她获得第 i 种卡牌的概率为 pi。如果这张卡牌之前已经获得过了,就会转化为一枚硬币。可以用 k 枚硬币交换一张没有获得过的卡。

小林会一直抽卡,直至集齐了所有种类的卡牌为止,求她的期望抽卡次数。如果你给出的答案与标准答案的绝对误差不超过 10−4,则视为正确。

提示:聪明的小林会把硬币攒在手里,等到通过兑换就可以获得剩余所有卡牌时,一次性兑换并停止抽卡。

输入格式

从标准输入读入数据。

输入共两行。第一行包含两个用空格分隔的正整数 n,k,第二行给出 p1,p2,…,pn,用空格分隔。

输出格式

输出到标准输出。

输出共一行,一个实数,即期望抽卡次数。

样例1输入

2 2
0.4 0.6

Data

样例1输出

2.52

Data

样例1解释

共有 2 种卡牌,不妨记为 A 和 B,获得概率分别为 0.4 和 0.6,2 枚硬币可以换一张卡牌。下面给出各种可能出现的情况:

  • 第一次抽卡获得 A,第二次抽卡获得 B,抽卡结束,概率为 0.4×0.6=0.24,抽卡次数为 2。
  • 第一次抽卡获得 A,第二次抽卡获得 A,第三次抽卡获得 B,抽卡结束,概率为 0.4×0.4×0.6=0.096,抽卡次数为 3。
  • 第一次抽卡获得 A,第二次抽卡获得 A,第三次抽卡获得 A,用硬币兑换 B,抽卡结束,概率为 0.4×0.4×0.4=0.064,抽卡次数为 3。
  • 第一次抽卡获得 B,第二次抽卡获得 A,抽卡结束,概率为 0.6×0.4=0.24,抽卡次数为 2。
  • 第一次抽卡获得 B,第二次抽卡获得 B,第三次抽卡获得 A,抽卡结束,概率为 0.6×0.6×0.4=0.144,抽卡次数为 3。
  • 第一次抽卡获得 B,第二次抽卡获得 B,第三次抽卡获得 B,用硬币兑换 A,抽卡结束,概率为 0.6×0.6×0.6=0.216,抽卡次数为 3。

因此答案是 0.24×2+0.096×3+0.064×3+0.24×2+0.144×3+0.216×3=2.52。

样例2输入

4 3
0.006 0.1 0.2 0.694

Data

样例2输出

7.3229920752

Data

子任务

对于 20% 的数据,保证 1≤n,k≤5。

对于另外 20% 的数据,保证所有 pi 是相等的。 

对于 100% 的数据,保证 1≤n≤16,1≤k≤5,所有的 pi 满足 pi≥110000,且 ∑i=1npi=1。

感觉代码没有错,数据也都能过,但是最后那个代码提交上去不对,实在不知道错在哪里了,希望各位大佬可以帮帮~~~,我在后面更新了一个记忆化搜索的答案,发现记忆化搜索真好用,可以用动态规划的好多问题都可以记忆化搜索。

代码如下:

#include <iostream>

using namespace std;
const int N=17,K=5;
double dp[77][1<<N];//dp[i][j]第i次抽卡,抽到状态为j时的概率
double p[N],q[1<<N];//p[i]表示第i个卡牌的概率,q[i]表示状态为i时,i中所有抽到的卡牌的概率之和
int cnt[1<<N];
void Count1(int n)
{
    for(int i=0;i<1<<n;i++)
    {
        int j=0,k=i;
        int num=0;
        while(k)
        {
           j++;
           if(k&1)
           {
               num++;
               q[i]+=p[j];
           }

           k=k>>1;
        }
        cnt[i]=num;
        //cout<<"cnt"<<i<<" "<<cnt[i]<<" ";
        //cout<<"q"<<i<<" "<<q[i]<<" ";

    }
}
int main()
{
    int n,l;
    cin>>n>>l;
    for(int i=1;i<=n;i++)
    {
        cin>>p[i];
    }
    int temp=l*(n-1)+1;
    double sum=0;
    Count1(n);
    dp[0][0]=1;//初始化
    for(int i=1;i<=temp;i++)//第i次抽卡的状态
    {
        for(int j=0;j<1<<n;j++)
        {
            int temp1=(n-cnt[j])*l;
            //if((i-cnt[j])>temp1) continue;
            if(cnt[j]<=i)
               for(int k=0;k<(1<<n)-1;k++)//第i-1次抽卡的状态
               {
                   int temp2=(n-cnt[k])*l;
                   if((cnt[k]<=i-1)&&temp2>(i-cnt[k]-1))
                      if((cnt[k]>=cnt[j]-1)&&((j&k)==k))
                      {
                         if((j&k)==j)
                          {
                             dp[i][j]+=dp[i-1][k]*q[j];

                          }
                         else
                         {
                            int x=j-k;
                            dp[i][j]+=dp[i-1][k]*q[x];

                         }
                       //cout<<"("<<i<<" "<<j<<" "<<k<<") :"<<dp[i][j]<<"  ";
                      }

               }

             if(j==(1<<n)-1)
            {
                sum+=dp[i][j]*i;
                    //cout<<i<<" "<<j<<" "<<dp[i][j]<<" "<<dp[i][j]*i<<" "<<" sum: "<<sum<<" ";
                continue;
            }
            if(temp1<=(i-cnt[j]))
            {
                sum+=dp[i][j]*i;
            //cout<<i<<" "<<j<<" "<<dp[i][j]<<" "<<dp[i][j]*i<<" "<<" sum: "<<sum<<" ";

            }
        }
    }
    //cout<<endl;
    cout<<sum;
    return 0;
}

记忆化搜索:(100分)

#include<iostream>
#include<cstdio>
using namespace std;
double dp[1<<17][81],p[17];
int n,k1;
double ans=0;
double dfs(int at,int k,int cnt)
{
    if(dp[at][k+cnt]) return dp[at][k+cnt];
    if(k+cnt/k1>=n) return (double)(k+cnt);
    double s=0;
    for(int i=0;i<n;i++)
    {
        if((at>>i)&1) s+=p[i]*dfs(at,k,cnt+1);
        else s+=p[i]*dfs(at+(1<<i),k+1,cnt);
    }
    dp[at][k+cnt]=s;
    return s ;
    
}
int main()
{
    cin>>n>>k1;
    for(int i=0;i<n;i++) cin>>p[i];
    //dp[0][0]=1;
   
    printf("%.10lf",dfs(0,0,0));
    //cout<<dfs(0,0,0);
	return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值