|
|
|
|
##[$AcWing$ $218$. 扑克牌 ](https://www.acwing.com/problem/content/description/220/)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
$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≤15$
|
|
|
|
|
|
|
|
|
|
**输入样例:**
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
1 2 3 4
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输出样例:**
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
16.393
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 二、题意分析
|
|
|
|
|
|
|
|
|
|
<font color='red' size=4><b>$Q$:为什么从终止状态向起始状态递推?</b></font>
|
|
|
|
|
|
|
|
|
|
**答**:满足条件的终止状态较多,而起始状态唯一。考虑以终止状态为初值,起始状态为目标,进行动态规划。
|
|
|
|
|
|
|
|
|
|
#### 状态表示
|
|
|
|
|
$f[a][b][c][d][x][y]$ : 当前已翻开状态下,还需翻开牌的数量 **期望数**。
|
|
|
|
|
|
|
|
|
|
- $a,b,c,d$ 为已翻开的各类牌 (黑红花片) 的数量
|
|
|
|
|
- $x,y$代表大、小王的状态($0$为未翻开,$1$代表已翻开且当做黑桃,以此类推), 设 $rst$ 为剩余牌的数量。
|
|
|
|
|
$$rst=54-a-b-c-d-(x!=0)-(y!=0)$$
|
|
|
|
|
|
|
|
|
|
若 $a < 13$,则当前抽到黑桃的贡献为
|
|
|
|
|
|
|
|
|
|
$$\frac{13−a}{rst} \times f[a+1][b][c][d][x][y]$$
|
|
|
|
|
|
|
|
|
|
其余花色同理。若小王被抽取,取可转移状态期望最小的一个进行状态转移,其贡献为:
|
|
|
|
|
|
|
|
|
|
$$\frac{1}{rst} \times \min_{1≤i≤4}f[a][b][c][d][i][y]$$
|
|
|
|
|
|
|
|
|
|
大王同理。
|
|
|
|
|
|
|
|
|
|
记忆化搜索求解,若无牌可抽仍未到达 $a > = A \&\& b > = B \&\& c > = C \&\& d > = D$ 的终止状态,则期望为正无穷,代表不合法的状态。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 三、实现代码
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 15;
|
|
|
|
|
const int INF = 0x3f3f3f3f;
|
|
|
|
|
double f[N][N][N][N][5][5];
|
|
|
|
|
int st[N][N][N][N][5][5];
|
|
|
|
|
int A, B, C, D;
|
|
|
|
|
|
|
|
|
|
//如果大小王翻出来放1里,则a++,放2里b++,...
|
|
|
|
|
void add(int &a, int &b, int &c, int &d, int x) {
|
|
|
|
|
if (x == 1) a++;
|
|
|
|
|
if (x == 2) b++;
|
|
|
|
|
if (x == 3) c++;
|
|
|
|
|
if (x == 4) d++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
功能:计算当前状态f(a,b,c,d,x,y)下的期望值
|
|
|
|
|
*/
|
|
|
|
|
double dfs(int a, int b, int c, int d, int x, int y) {
|
|
|
|
|
//记忆化,同时因为f为double类型,不能使用传统的memset(0x3f)之类
|
|
|
|
|
//进行初始化并判断是否修改过,只能再开一个st数组
|
|
|
|
|
if (st[a][b][c][d][x][y]) return f[a][b][c][d][x][y];
|
|
|
|
|
st[a][b][c][d][x][y] = 1;
|
|
|
|
|
|
|
|
|
|
//递归出口:当前状态是否到达目标状态,目标状态的期望值是0
|
|
|
|
|
int ta = a, tb = b, tc = c, td = d; //抄出来
|
|
|
|
|
add(ta, tb, tc, td, x), add(ta, tb, tc, td, y); //大王小王会改变四个花色的数量
|
|
|
|
|
if (ta >= A && tb >= B && tc >= C && td >= D) return 0;
|
|
|
|
|
|
|
|
|
|
//当前状态下的剩余牌数量
|
|
|
|
|
int rst = 54 - ta - tb - tc - td;
|
|
|
|
|
if (rst == 0) return INF; //还没有完成目标,没有剩余的牌了,无解
|
|
|
|
|
|
|
|
|
|
//当前状态可以向哪些状态转移
|
|
|
|
|
// Q:v为什么要初始化为1?
|
|
|
|
|
// A:看题解内容
|
|
|
|
|
double v = 1;
|
|
|
|
|
if (a < 13) //黑桃有剩余,可能选出的是黑桃
|
|
|
|
|
v += dfs(a + 1, b, c, d, x, y) * (13 - a) / rst;
|
|
|
|
|
if (b < 13) //红桃有剩余,可能选出的是红桃
|
|
|
|
|
v += dfs(a, b + 1, c, d, x, y) * (13 - b) / rst;
|
|
|
|
|
if (c < 13) //梅花有剩余,可能选出的是梅花
|
|
|
|
|
v += dfs(a, b, c + 1, d, x, y) * (13 - c) / rst;
|
|
|
|
|
if (d < 13) //方块有剩余,可能选出的是方块
|
|
|
|
|
v += dfs(a, b, c, d + 1, x, y) * (13 - d) / rst;
|
|
|
|
|
|
|
|
|
|
//如果小王没有被选出
|
|
|
|
|
if (x == 0)
|
|
|
|
|
v += min(min(dfs(a, b, c, d, 1, y), dfs(a, b, c, d, 2, y)), min(dfs(a, b, c, d, 3, y), dfs(a, b, c, d, 4, y))) / rst;
|
|
|
|
|
|
|
|
|
|
//如果大王没有被选出
|
|
|
|
|
if (y == 0)
|
|
|
|
|
v += min(min(dfs(a, b, c, d, x, 1), dfs(a, b, c, d, x, 2)), min(dfs(a, b, c, d, x, 3), dfs(a, b, c, d, x, 4))) / rst;
|
|
|
|
|
|
|
|
|
|
return f[a][b][c][d][x][y] = v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
cin >> A >> B >> C >> D;
|
|
|
|
|
//① 终点状态不唯一,起点是唯的的,所以以起点为终点,以终点为起点,反着推
|
|
|
|
|
//② AcWing 217. 绿豆蛙的归宿 需要建图,本题不用建图
|
|
|
|
|
double res = dfs(0, 0, 0, 0, 0, 0); //四种花色、大小王都还没有被抽取
|
|
|
|
|
|
|
|
|
|
if (res > INF / 2) //因为是浮点数,不能用等号判断是不是相等,简单的办法就是INF/2
|
|
|
|
|
puts("-1.000");
|
|
|
|
|
else
|
|
|
|
|
printf("%.3f\n", res);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 四、期望值为什么初始化为$1$?
|
|
|
|
|
|
|
|
|
|
$f[i]$: 从$i$卡牌状态到终点状态所需要的**期望卡牌数**
|
|
|
|
|
|
|
|
|
|
每次抽一张牌变到下个状态,所以每条路径的权值为$1$
|
|
|
|
|
|
|
|
|
|
$$\large f[v]=p_1×(f[1]+1)+p_2×(f[2]+1)+p_3×(f[3]+1)+…+p_k×(f[k]+1) = \\
|
|
|
|
|
\sum_{i=1}^{k}p_i+\sum_{i=1}^{k}p_i \times f[i]
|
|
|
|
|
$$
|
|
|
|
|
|
|
|
|
|
因为$v$一定能到达下个局面,所以下个状态的概率和为$1$,这里的$\large \displaystyle \sum_{i=1}^{k}p_i=1$ 那么就有:$\displaystyle \large f[v]=1+\sum_{i=1}^{k}p_i \times f[i]$
|
|
|
|
|
综上这里的$f[v]$可以初始化为$1$!
|
|
|
|
|
<center><img src='https://img-blog.csdnimg.cn/4479c699431c421da4ab5d4f3f3db1bc.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUxOTY4MTU1,size_16,color_FFFFFF,t_70'></center>
|