概率DP
要抽象出:起点、中点
转移的时候:一个点转移到n个点,然后记忆化
题目
Admin生日那天,Rainbow来找Admin玩扑克牌。
玩着玩着Rainbow觉得太没意思了,于是决定给Admin一个考验。
Rainbow把一副扑克牌(54张)随机洗开,倒扣着放成一摞。
然后Admin从上往下依次翻开每张牌,每翻开一张黑桃、红桃、梅花或者方块,就把它放到对应花色的堆里去。
Rainbow想问问Admin,得到A张黑桃、B张红桃、C张梅花、D张方块需要翻开的牌的张数的期望值E是多少?
特殊地,如果翻开的牌是大王或者小王,Admin将会把它作为某种花色的牌放入对应堆中,使得放入之后E的值尽可能小。
由于Admin和Rainbow还在玩扑克,所以这个程序就交给你来写了。
输入格式
输入仅由一行,包含四个用空格隔开的整数,A,B,C,D。
输出格式
输出需要翻开的牌数的期望值E,四舍五入保留3位小数。
如果不可能达到输入的状态,输出-1.000。
数据范围
0≤A,B,C,D≤150≤A,B,C,D≤15
输入样例:
1 2 3 4
输出样例:
16.393
扑克牌的期望DP
- 状态转移:抽出一张牌放入分类
- 状态表示:每种牌有多少个,大王、小王代替什么牌
- 转移:按照当前取的牌放到哪种,可以由一个状态变为多个状态
- 起点:当前没有拿牌
- 终点:把牌放完,终点概率为0
- 拿一张牌的期望为1,在此基础上+=其他可以变化的状态
#include<iostream>
#include<map>
#include<cmath>
#include<string.h>
using namespace std;
typedef long long ll;
const int N=17;
const double INF=1e20;
double f[N][N][N][N][5][5];
int A,B,C,D;
double dp(int a,int b,int c,int d,int x,int y)
{
double &v = f[a][b][c][d][x][y];
if(v>=0) return v;
int sa=a+(x==0) + (y==0);
int sb=b+(x==1) + (y==1);
int sc=c+(x==2) + (y==2);
int sd=d+(x==3) + (y==3);
if(sa>=A && sb>=B && sc >=C && sd >=D) return 0;
int sum = sa + sb+ sd+sc;
//int sum=a+b+c+d+(x!=4) + (y!=4);
sum=54-sum;
if(sum<=0) return INF;
v=1;// 转移的权重就是1(翻一张卡牌)
if( a<13) v+= (13.0-a)/sum*dp(a+1,b,c,d,x,y) ;
if( b<13) v+= (13.0-b)/sum*dp(a,b+1,c,d,x,y) ;
if(c<13) v+= (13.0-c)/sum*dp(a,b,c+1,d,x,y) ;
if(d<13) v+= (13.0-d)/sum*dp(a,b,c,d+1,x,y) ;
if(x==4)
{
double minv=INF;
for(int i=0;i<4;i++)
minv=min(minv, 1.0/sum*dp(a,b,c,d,i,y));
v+=minv;
}
if(y==4)
{
double minv=INF;
for(int i=0;i<4;i++)
minv=min(minv, 1.0/sum*dp(a,b,c,d,x,i));
v+=minv;
}
return v;
}
int main()
{
cin>>A>>B>>C>>D;
memset(f,-1,sizeof f);
double t=dp(0,0,0,0,4,4);
if(t>=INF/2) cout<<"-1.000"<<endl;
else printf("%.3lf\n" ,t);
return 0;
}