(CCF202109-4)收集卡牌(概率DP)

题目链接:计算机软件能力认证考试系统

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

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

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

输入格式

从标准输入读入数据。

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

输出格式

输出到标准输出。

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

样例1输入

2 2
0.4 0.6

Data

样例1输出

2.52

 

分析:这显然是一道概率DP题目,我么可以令f[i][j]表示当前收集卡牌状态为i且当前有k个硬币距离集齐所需要次数的期望,那么答案显然就是f[0][0],我们用记忆化搜索去查找f[0][0],我们下面进行分析搜索函数的写法:

首先来看一下递归终止条件,不妨假设有n张不同的卡牌,每k个硬币可以换一张卡牌,肯定是s全为1也就是s的二进制表示中含有n个1时退出,或者s二进制表示中的0的个数乘以k小于等于t,也就是说我们现在所拥有的硬币可以把缺少的牌全部换回来,这就是我们的递归终止条件了,递归体就比较容易写了,我们看看f[s][k]可以更新至哪些状态,首先可以更新至f[s][k+1],也就是说我们抽到了已经存在的一张牌自动转换为一个硬币,还有就是f[s|(1<<i)][k],也就是说我们抽到了我们之前不含有的一张牌,硬币数目不变,但是我们的状态会发生变化,还有需要注意的一点是不要忘记乘以其对应的概率p[i],再加上当前抽取牌所消耗的一次机会,这一部分我们是在递归函数中用for循环枚举的。考虑到这些这道题目基本上就解决了。

下面是代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
double f[1<<20][103];//f[i][j]表示当前收集卡牌状态为i且当前有k个硬币距离集齐所需要次数的期望 
double p[20];
int n,k;
double dp(int s,int t)//fp(s,t)返回当前收集卡牌状态为s且当前有t个硬币距离集齐所需要次数的期望
{
	int cnt=0;
	for(int i=0;i<n;i++)
		if(s>>i&1) cnt++;
	if((n-cnt)*k<=t) return 0;//可以用硬币直接交换 
	if(f[s][t]) return f[s][t];
	for(int i=0;i<n;i++)
	{
		if(s>>i&1)
			f[s][t]+=p[i]*(dp(s,t+1)+1);
		else
			f[s][t]+=p[i]*(dp(s|(1<<i),t)+1);
	}
	return f[s][t];
}

int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++)
		scanf("%lf",&p[i]);
	printf("%.10lf",dp(0,0));
	return 0; 
} 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值