AcWing 218. 扑克牌


题目传送门

一、题意分析

我们使用\(F(a, b, c, d, x, y)\)表示现在已经翻出\(a\)张黑桃,\(b\)张红桃,\(c\)张梅花,\(d\)张方块,且大小王的状态为\(x\)\(y\)。以小王为例,当\(x=4\)时,表示小王还没翻出来,\(x≠4时\),\(x\)\(0,1,2,3\),分别表示小王放入了四个花色的牌堆中,\(0\):黑桃、\(1\):红桃、\(2\):梅花、\(3\):方块。

我们把问题转化成有向无环图,每个节点表示一个状态,边权是\(1\),表示翻出的牌数加\(1\)

起点:\(F[0, 0, 0, 0, 4, 4]\)

终点:
\(\displaystyle a + (x == 0) + (y == 0)\geqslant A \\ b + (x == 1) + (y == 1) \geqslant B \\ c + (x == 2)+(y == 2) \geqslant C \\ d + (x == 3) + (y == 3) \geqslant D\)
从起点到终点的期望路径。

由假设当前的节点是\(x\),出边指向\(y_1, y_2, ...y_k\). 走向不同的出边的概率分别是\(p_1,p_2,... p_k\).

那么节点\(x\)的期望值是:$$\LARGE F[x] = \sum_{i=1}^{k}p[i] * (F[y_i] + 1)$$

接下来我们求\(p[i]\),假设我们当前已经抽取了\(sum\)张牌:

\[\large sum=a+b+c+d+(x≠4)+(y≠4) \]

还剩下\(54-sum\)张牌,其中有\(13-a\)张黑桃.故翻开一张黑桃的概率为\((13 - a) / (54 - sum)\), 状态转移为\(F(a + 1, b, c, d, x, y)\).其余\(3\)个花色同理.

特别地,当\(x=4\)时,有\(1/(54-sum)\)的概率抽到小王,根据题意,小王变成某个花色后,期望值要尽可能的小,即

\[\large \min_{0<=x'<=3}(F[a,b,c,d,x',y])$$,大王同理. 综上所述,状态的转移为 $$\large F[a,b,c,d,x,y]=1+\frac{13-a}{54-sum}\times F[a+1,b,c,d,x,y] \\ +\frac{13-b}{54-sum}\times F[a,b+1,c,d,x,y] \\ +\frac{13-c}{54-sum}\times F[a,b,c+1,d,x,y] \\ +\frac{13-d}{54-sum}\times F[a,b,c,d+1,x,y] \\ +\frac{1}{54-sum}\times \min_{0<=x'<=3}(F[a,b,c,d,x',y]) \\ +\frac{1}{54-sum}\times \min_{0<=y'<=3}(F[a,b,c,d,x,y']) \]

二、实现代码

#include 

using namespace std;

const int N = 14;           //每个花色共13张牌
const double INF = 1e20;    //在 double情况下的极大值,yxc用了1e20
int A, B, C, D;             //四个花色需要达到的个数
double f[N][N][N][N][5][5]; //黑红花片+小王+大王

//找出递推关系式,递推=dp
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); // 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 0; 

    int sum = a + b + c + d + (x != 4) + (y != 4); // 4表示还没有被翻出; x!=4 表示小王已经被翻出,总数需要+1,大王同理
    sum = 54 - sum;                                //剩余牌数量
    if (sum <= 0) return INF;                      //没有牌了,返回正无穷

    v = 1; // 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); //标识未使用状态,-1代表没算过,0表示算过,结果是0

    double t = dp(0, 0, 0, 0, 4, 4); //结果保存到f(0,0,0,0,4,4)里面,从结果向前倒推

    //这是啥意思?
    if (t > INF / 2) t = -1;

    //结果保留小数点后3位小数
    printf("%.3lf\n", t);
    return 0;
}

三、\(Q\):期望值为什么可以初始化为\(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\)

相关