You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 218. 扑克牌

一、题目描述

Admin 生日那天,Rainbow 来找 Admin 玩扑克牌。

玩着玩着 Rainbow 觉得太没意思了,于是决定给 Admin 一个考验。

Rainbow 把一副扑克牌(54张)随机洗开,倒扣着放成一摞。

然后 Admin 从上往下依次翻开每张牌,每翻开一张黑桃、红桃、梅花或者方块,就把它放到对应花色的堆里去。

Rainbow 想问问 Admin,得到 A 张黑桃、B 张红桃、C 张梅花、D 张方块需要翻开的牌的张数的期望值 E 是多少?

特殊地,如果翻开的牌是大王或者小王,Admin 将会把它作为某种花色的牌放入对应堆中,使得放入之后 E的值尽可能小。

由于 AdminRainbow 还在玩扑克,所以这个程序就交给你来写了。

输入格式 输入仅由一行,包含四个用空格隔开的整数,A,B,C,D

输出格式 输出需要翻开的牌数的期望值 E,四舍五入保留 3 位小数。

如果不可能达到输入的状态,输出 -1.000

数据范围 0≤A,B,C,D≤15

输入样例:

1 2 3 4

输出样例:

16.393

二、题意分析

Q:为什么从终止状态向起始状态递推?

:满足条件的终止状态较多,而起始状态唯一。考虑以终止状态为初值,起始状态为目标,进行动态规划。

状态表示

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{13a}{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 的终止状态,则期望为正无穷,代表不合法的状态。

三、实现代码

#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