扑克牌 题解
扑克牌 题解
题意
$\ \ \ \ \ \ $ 有一副扑克牌共 \(54\) 张,包括黑桃、红桃、梅花和方块四种花色各 \(13\) 张与 \(2\) 张大小王。将这副牌反面朝上放置,每次翻一张牌,若翻到大小王可以将其视作任何花色。求最后翻出 \(A\) 张黑桃、\(B\) 张红桃、\(C\) 张梅花和 \(D\) 张方块的最小期望翻牌次数是多少。
题解
\(~~~~\) 计黑桃为第一种花色,红桃为第二种花色,梅花为第三种花色,方块为第四种花色。
\(~~~~\) 一道定义为入门期望DP的题,虽然我当时想了挺久。
\(~~~~\) 显然DP至少会定义四个维度表示四种花色拥有的张数。考虑如何处理大小王。
\(~~~~\) 因此定义 \(dp_{a,b,c,d,x,y}\) ,表示翻开 \(a\) 张黑桃、\(b\) 张红桃、\(c\) 张梅花、\(d\) 张方块,且小王用作第 \(x\) 种花色,大王用作第 \(y\) 种花色后凑到要求的种类的期望翻牌最小张数。当 \(x=0\) 时表示未翻开小王, \(y=0\) 时表示为翻开大王。
\(~~~~\) 考虑一个已知的 \(dp_{a,b,c,d,x,y}\) 能扩展出哪些状态。
\(~~~~\) 以黑桃为例,当你有 \(a\) 张黑桃,已经翻开 \(k\) 张牌时,有 \(\dfrac{13-a}{54-k}\) 的概率再翻出一张黑桃。
\(~~~~\) 今天学到了一个结论,当某事件发生的概率为 \(p\) 时,则其期望在 \(\dfrac{1}{p}\) 次实验后第一次出现该事件。具体可以看 知乎上一个回答 (蒟蒻的我只会用结论)。所以就会在期望 \(\dfrac{54-k}{13-a}\) 次翻牌后第一次翻到一张黑桃。所以当你再翻出来一张黑桃时,状态就变成了 \(dp_{a+1,b,c,d,x,y}\) 。由期望的性质 \(\operatorname{E(XY)=E(X) \times E(Y)}\) 可以得到 \(dp_{a+1,b,c,d,x,y}\) 一定有贡献来自 \(dp_{a,b,c,d,x,y} \times \dfrac{54-k}{13-a}\) .另外三种花色也是同理。
\(~~~~\) 再来考虑双王的问题,若一个状态 \(dp_{a,b,c,d,0,y}\) ,此时小王未被翻出来,因此已经翻开 \(k\) 张牌时,有 \(\dfrac{1}{54-k}\) 的概率翻出小王。我们按照上面的方式化简,得到 \(dp_{a,b,c,d,x,y}\) 也有来自 \(dp_{a,b,c,d,0,y}\times (54-k)\) 的贡献,其中 \(x\in[1,4]\)。
\(~~~~\) 再来考虑初始状态,可以发现当四种花色及双王满足条件时,其 \(dp\) 值为 \(0\) ,而要求的答案就是 \(dp_{0,0,0,0,0,0}\) ,因此本题是逆推。
\(~~~~\) 既然如此,则每个 \(dp_{a,b,c,d,x,y}\) 一定依托于多翻一张牌的状态。将上面所有式子移项即可。当考虑双王时,可以得到 \(\dfrac{dp_{a,b,c,d,x,y}}{54-k}\ (1\le x\le 4)\) 都有可能是 \(dp_{a,b,c,d,0,y}\) 的一部分加数。因为求最小期望,我们取四个值得最小值即可。
\(~~~~\) 综上,本题的DP转移方程是 :
\(~~~~\) 当然,每次转移前要先判断 \(a+1\),\(b+1\) 等的值是否合法。
\(~~~~\) 实现的话记忆化搜索比打递推要方便一些,这里就打的记忆化搜索。
代码
查看代码
#include <cstdio>
#include <algorithm>
#define f0 dp[a][b][c][d][x][y]
#define f1 dfs(a+1,b,c,d,x,y)
#define f2 dfs(a,b+1,c,d,x,y)
#define f3 dfs(a,b,c+1,d,x,y)
#define f4 dfs(a,b,c,d+1,x,y)
using namespace std;
double dp[15][15][15][15][5][5];
int A,B,C,D;
inline bool check(int a,int b,int c,int d,int x,int y)
{
if(a+(x==1)+(y==1)>=A&&b+(x==2)+(y==2)>=B&&c+(x==3)+(y==3)>=C&&d+(x==4)+(y==4)>=D) return true;
else return false;
}
double dfs(int a,int b,int c,int d,int x,int y)
{
if(f0) return f0;
if(check(a,b,c,d,x,y)) return 0;
int sum=a+b+c+d+(x!=0)+(y!=0);
double cnt=1;
if(a<13) cnt+=f1*(13-a)/(54-sum);
if(b<13) cnt+=f2*(13-b)/(54-sum);
if(c<13) cnt+=f3*(13-c)/(54-sum);
if(d<13) cnt+=f4*(13-d)/(54-sum);
if(x==0)
{
double tmp=1000;
for(int i=1;i<=4;i++) tmp=min(tmp,dfs(a,b,c,d,i,y)/(54-sum));
cnt+=tmp;
}
if(y==0)
{
double tmp=1000;
for(int i=1;i<=4;i++) tmp=min(tmp,dfs(a,b,c,d,x,i)/(54-sum));
cnt+=tmp;
}
return dp[a][b][c][d][x][y]=cnt;
}
int main() {
scanf("%d %d %d %d",&A,&B,&C,&D);
if(max(0,A-13)+max(0,B-13)+max(0,C-13)+max(0,D-13)>2)
{
printf("-1.000");
return 0;
}
double ans=dfs(0,0,0,0,0,0);
printf("%.3lf",ans);
return 0;
}