218 扑克牌(数学期望)

1. 问题描述:

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

输入样例:
1 2 3 4

输出样例:
16.393
来源:https://www.acwing.com/problem/content/description/220/

2. 思路分析:

分析题目可以知道这道题目求解的是数学期望,所以可以将其转换为dp来解决,因为题目中涉及到黑桃、红桃、梅花、方块、大王或者是小王,所以可以声明一个六维的f,其中f[a][b][c][d][x][y]表示当前黑桃有a张,红桃有b张,梅花有c张,方块有d张,大王的状态为x,小王的状态为y的所有方案的期望值,其中x,y = {0,1,2,3}表示放到黑桃,红桃,梅花,方块中,当前的状态值f[a][b][c][d][x][y]取决于下一张牌是什么:

s = a + b + c + d + (x != 4) + (y!= 4),黑桃:(13 - a) / (54 - s) * f(a + 1,b,c,d,x,y);红桃: (13 - b) / (54 - s) * f(a,b + 1,c,d,x,y);梅花:(13 - c) / (54 - s) * f(a,b,c + 1,d,x,y);方块:(13 - d) / (54 - s) * f(a,b,c,d + 1,x,y);大王和小王,枚举一下当前可以放到哪一个堆中即可,最终f(0,0,0,0,4)就是答案。

3. 代码如下:

c++:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 14;
const double INF = 1e20;
int A, B, C, D;
double f[N][N][N][N][5][5];

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 as = a + (x == 0) + (y == 0);
    int bs = b + (x == 1) + (y == 1);
    int cs = c + (x == 2) + (y == 2);
    int ds = d + (x == 3) + (y == 3);
    if (as >= A && bs >= B && cs >= C && ds >= D) return v = 0;
    int sum = a + b + c + d + (x != 4) + (y != 4);
    sum = 54 - sum;
    if (sum <= 0) return v = INF;
    v = 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 t = INF;
        for (int i = 0; i < 4; i ++ ) t = min(t, 1.0 / sum * dp(a, b, c, d, i, y));
        v += t;
    }
    if (y == 4)
    {
        double t = INF;
        for (int i = 0; i < 4; i ++ ) t = min(t, 1.0 / sum * dp(a, b, c, d, x, i));
        v += t;
    }

    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) t = -1;

    printf("%.3lf\n", t);

    return 0;
}

python(超时):

class Solution:
    INF = 10 ** 20
    A = B = C = D = 0
    
    # 记忆化搜索
    def dp(self, a: int, b: int, c: int, d: int, x: int, y: int, f):
        v = f[a][b][c][d][x][y]
        if v >= 0: return v
        asum = a + (1 if x == 0 else 0) + (1 if y == 0 else 0)
        bsum = b + (1 if x == 1 else 0) + (1 if y == 1 else 0)
        csum = c + (1 if x == 2 else 0) + (1 if y == 2 else 0)
        dsum = d + (1 if x == 3 else 0) + (1 if y == 3 else 0)
        # 判断当前的状态是否满足条件
        if asum >= self.A and bsum >= self.B and csum >= self.C and dsum >= self.D: return 0
        # 计算当前牌的数量
        s = a + b + c + d + (1 if x != 4 else 0) + (1 if y != 4 else 0)
        # 计算剩余的牌的数量
        s = 54 - s
        if s <= 0: return self.INF
        # 当前牌的平均长度为1
        v = 1.0
        if a < 13:
            v += (13.0 - a) / s * self.dp(a + 1, b, c, d, x, y, f)
        if b < 13:
            v += (13.0 - b) / s * self.dp(a, b + 1, c, d, x, y, f)
        if c < 13:
            v += (13.0 - c) / s * self.dp(a, b, c + 1, d, x, y, f)
        if d < 13:
            v += (13.0 - d) / s * self.dp(a, b, c, d + 1, x, y, f)
        # 当前大王还存在
        if x == 4:
            t = self.INF
            for i in range(4):
                t = min(t, 1.0 / s * self.dp(a, b, c, d, i, y, f))
            v += t
        # 当前小王还存在
        if y == 4:
            t = self.INF
            for i in range(4):
                t = min(t, 1.0 / s * self.dp(a, b, c, d, x, i, f))
            v += t
        f[a][b][c][d][x][y] = v
        return v

    # 数学期望
    def process(self):
        self.A, self.B, self.C, self.D = map(int, input().split())
        # 六维列表
        f = [[[[[[-1] * 5 for i in range(5)]
                for j in range(14)]
                for k in range(14)]
                for l in range(14)]
                for m in range(14)]
        INF = 10 ** 20
        res = self.dp(0, 0, 0, 0, 4, 4, f)
        # 为了避免精度的问题所以取INF / 2作为判断
        if res > INF / 2: res = -1
        return "{:.3f}".format(res)


if __name__ == "__main__":
    print(Solution().process())
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值