题意

你要和一堆骰子玩剪刀石头布。有 $n$ 个骰子,每个骰子有三种结果:R,S,P。概率分别是 $pR_i,pS_i,pP_i$。每次你会随机选择一个没有用过的骰子,然后你决定好出什么,然后扔这个骰子。如果获胜得 $3$ 分,平局得 $1$ 分,失败得 $0$ 分。你无法分辨出每一次拿出的骰子是什么,但你可以通过每一局的结果来决策之后的操作。求期望最多可以获得多少得分。

$1 \leq n \leq 50$。

题解

由于玩家可以得到的唯一局面信息是当前骰子出了多少个 RSP。所以我们记录这三个信息 $(i,j,k)$,就可以唯一确定一个局面。

对于一个局面,我们的决策是依赖于当前局面产生 RSP 的概率来计算的。所以我们就需要对于每个 $(i,j,k)$,分别得到下一局产生 RSP 的概率。

考虑如何暴力做这个事情:先枚举哪些骰子是已经使用过的,并且枚举它们的结果。要求结果和 $(i,j,k)$ 相同。然后我们得到了一个概率 $p$,然后将剩下的所有骰子的 $p_r,p_s,p_p$ 都加起来,乘上这个概率 $p$,贡献到答案中。这样最终得到的 R,S,P 的结果在整体中的比例就反应了这个手势出现的概率。

所以我们可以用 dp 做这个事情:设 $g_{a,i,j,k}$ 表示考虑了前 $a$ 个骰子,局面 $(i,j,k)$ 的时候,记录的三种手势的权值。转移枚举当前骰子是否用过,如果用过枚举结果并直接乘上相应的概率;否则我们要把这个权值记录到 $g$ 中,发现直接加不太对,因为我们这个 $g$ 实际上是记录的很多方案的和,每个方案都是一堆系数乘上没有用过的骰子的 $p_r,p_s,p_p$ 的和。所以我们需要同时记录一个系数和就好了。复杂度 $O(n^4)$。

然后就可以直接 dp 算答案了:设 $f_{i,j,k}$ 表示局面是 $(i,j,k)$ 的最优答案,转移枚举这一步出什么,根据 $g$ 算出的概率计算一下期望收益即可。这一部分复杂度 $O(n^3)$。

总复杂度 $O(n^4)$。

代码

#include <bits/stdc++.h>

#define fi first
#define se second
#define DB double
#define U unsigned
#define LL long long
#define LD long double
#define pb emplace_back
#define MP std::make_pair
#define SZ(x) ((int)x.size())
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 50 + 5;
const DB EPS = 1e-15;

class RockPaperScissors{
private:
    DB r[MAXN],p[MAXN],s[MAXN];int n;
    struct Node{
        DB r,p,s,c;
        Node(DB r=0,DB p=0,DB s=0,DB c=0) : r(r),p(p),s(s),c(c) {}

        Node operator + (const Node &t){
            return Node(r+t.r,p+t.p,s+t.s,c+t.c);
        }

        Node operator * (const DB &t){
            return Node(r*t,p*t,s*t,c*t);
        }
    }g[2][MAXN][MAXN][MAXN];
    // 已经出现 j 个 r,k 个 p,l 个 s,三者出现概率
    DB f[MAXN][MAXN][MAXN];int now;
    bool vis[MAXN][MAXN][MAXN];

    inline DB dp(int i,int j,int k){
        if(i+j+k == n) return 0;
        if(vis[i][j][k]) return f[i][j][k];
        vis[i][j][k] = 1;
        DB &res = f[i][j][k];
        Node t = g[now][i][j][k];
        DB sm = t.r+t.p+t.s;
        if(sm == 0) return res = -1e9;
        t.r /= sm;t.p /= sm;t.s /= sm;
        // 选择 R
        res = t.r*(dp(i+1,j,k)+1)+t.p*(dp(i,j+1,k))+t.s*(dp(i,j,k+1)+3);
        res = std::max(res,t.r*(dp(i+1,j,k)+3)+t.p*(dp(i,j+1,k)+1)+t.s*(dp(i,j,k+1)));
        res = std::max(res,t.r*(dp(i+1,j,k))+t.p*(dp(i,j+1,k)+3)+t.s*(dp(i,j,k+1)+1));
        return res;
    }
public:
    DB bestScore(std::vector<int> _R,std::vector<int> _P,std::vector<int> _S){
        n = SZ(_R);FOR(i,1,n) r[i] = 1.0*_R[i-1]/300,p[i] = 1.0*_P[i-1]/300,s[i] = 1.0*_S[i-1]/300;
        now = 0;CLR(g[now],0);g[now][0][0][0].c = 1;
        FOR(i,1,n){
            CLR(g[now^1],0);
            FOR(j,0,i-1){
                FOR(k,0,i-1-j){
                    FOR(l,0,i-1-j-k){
                        Node t = g[now][j][k][l];
                        g[now^1][j+1][k][l] = g[now^1][j+1][k][l]+(t*r[i]);
                        g[now^1][j][k+1][l] = g[now^1][j][k+1][l]+(t*p[i]);
                        g[now^1][j][k][l+1] = g[now^1][j][k][l+1]+(t*s[i]);
                        g[now^1][j][k][l] = g[now^1][j][k][l]+t+(Node(r[i],p[i],s[i],0)*t.c);
                    }
                }
            }
            now ^= 1;
        }
        return dp(0,0,0);
    }
};
Last modification:June 5th, 2021 at 03:30 pm
如果觉得我的文章对你有用,请随意赞赏