【訓練題19:概率DP】One Person Game | ZOJ3329

溢流眼淚發表於2020-12-09

One Person Game | ZOJ3329

難度

6 / 10 6/10 6/10
有一說一,是一道經典概率DP題,個人感覺至少省賽題。
但是看其他部落格都是隨便秒過的 ???

題意

你有三個骰子,分別有 K 1 , K 2 , K 3 K_1,K_2,K_3 K1,K2,K3面,每面投擲到的概率都相等,面上標號數字為 1 ∼ K i 1\sim K_i 1Ki

一開始,你的分數為 0 0 0
每輪:你同時投擲三個骰子,如果第一個骰子頂面為 a a a 面 , 第二個骰子頂面為 b b b 面, 第三個頂面骰子為 c c c 面 ,那麼你的分數歸零。否則,你的分數增加三個骰子的頂面的和。
如果你的分數超過 n n n,遊戲結束

問你:遊戲開始到遊戲結束,你的投擲骰子次數的期望為多少?
要求答案誤差 ∣ e p s ∣ < 1 0 − 8 |eps|<10^{-8} eps<108

樣例輸入

組數, T T T
n   K 1   K 2   K 3   a   b   c n\ K_1\ K_2\ K_3\ a\ b\ c n K1 K2 K3 a b c

2 0   2   2   2   1   1   1 0   6   6   6   1   1   1 2\\ 0\ 2\ 2\ 2\ 1\ 1\ 1\\ 0\ 6\ 6\ 6\ 1\ 1\ 1 20 2 2 2 1 1 10 6 6 6 1 1 1

樣例輸出

1.142857142857143 1.004651162790698 1.142857142857143\\ 1.004651162790698 1.1428571428571431.004651162790698

思路

  • 首先,我們按照前人的經驗,期望得倒著推
  • e ( i ) \color{cyan}e(i) e(i) 表示目前分數為 i i i , 到遊戲結束時的投擲次數的期望。
  • f ( i ) \color{cyan}f(i) f(i) 表示三個骰子投擲出點數和為 i i i 的概率。
  • 特別地,設 f ( 0 ) f(0) f(0) 表示投擲後,第一個骰子頂面為 a a a 面 , 第二個骰子頂面為 b b b 面, 第三個頂面骰子為 c c c 面的概率。即,每次分數歸零的概率。
  • 目前分數為 i i i , 我們投擲一次之後三面骰子分別為 w 1 , w 2 , w 3 w_1,w_2,w_3 w1,w2,w3
    • 下次的分數可能為 i + w 1 + w 2 + w 3 i+w_1+w_2+w_3 i+w1+w2+w3,這個概率為 f ( w 1 + w 2 + w 3 ) f(w_1+w_2+w_3) f(w1+w2+w3)
    • 下次的分數可能為 0 0 0 ,就是歸零了,這個概率為 f ( 0 ) f(0) f(0)
  • 易得,如果已經分數超過 n n n 了,那麼遊戲直接結束,投擲次數期望為0,即:
    • e ( > n ) = 0 e(>n)=0 e(>n)=0
    • 我們最終的答案即為 e ( 0 ) e(0) e(0)
  • 我們寫成式子就是 e ( i ) = ∑ K 1 + K 2 + K 3 k = 1 f ( k ) e ( i + k ) + f ( 0 ) e ( 0 ) + 1 \color{red}e(i)=\underset{k=1}{\overset{K_1+K_2+K_3}{\sum}} f(k)e(i+k) + f(0)e(0)+1 e(i)=k=1K1+K2+K3f(k)e(i+k)+f(0)e(0)+1
  • (加一是因為多投擲了一次)
  • 注意到,我們期望是逆推的 ( i i i 從大到小),答案為 e ( 0 ) e(0) e(0),但是其中每一個遞推式子都包括 e ( 0 ) e(0) e(0),這就很頭疼了!

【注】如果 e ( i ) e(i) e(i) 每一項遞推式是關於 e ( x ) ∧ ( x ≥ i ) e(x)\wedge (x\ge i) e(x)(xi),我們可以把遞推式子的右邊的 e ( i ) e(i) e(i) 項拎出來放在左邊,然後轉化為一個左邊只有 e ( i ) e(i) e(i) , 右邊關於 e ( x ) ∧ ( x > i ) e(x)\wedge (x> i) e(x)(x>i)的式子,可以直接遞推。

這 題 的 套 路 做 法 \color{red}這題的套路做法

  • e ( i ) = a ( i ) e ( 0 ) + b ( i ) \color{cyan}e(i)=a(i)e(0)+b(i) e(i)=a(i)e(0)+b(i)
  • 我們帶入上述式子,得到:
    e ( i ) = ∑ f ( k ) e ( i + k ) + f ( 0 ) e ( 0 ) + 1 = ∑ f ( k ) ( a ( i + k ) e ( 0 ) + b ( i + k ) ) + f ( 0 ) e ( 0 ) + 1 = ( ∑ f ( k ) a ( i + k ) + f ( 0 ) ) e ( 0 ) + ( ∑ f ( k ) b ( i + k ) + 1 ) = a ( i ) e ( 0 ) + b ( i ) \begin{aligned}e(i)&=\sum f(k)e(i+k)+f(0)e(0)+1\\ &=\sum f(k)\Big( a(i+k)e(0)+b(i+k) \Big)+f(0)e(0)+1\\ &=\Big(\sum f(k)a(i+k)+f(0)\Big)e(0)+\Big( \sum f(k)b(i+k)+1 \Big)\\ &=a(i)e(0)+b(i) \end{aligned} e(i)=f(k)e(i+k)+f(0)e(0)+1=f(k)(a(i+k)e(0)+b(i+k))+f(0)e(0)+1=(f(k)a(i+k)+f(0))e(0)+(f(k)b(i+k)+1)=a(i)e(0)+b(i)
  • 我們得到了:
    { a ( i ) = ∑ f ( k ) a ( i + k ) + f ( 0 ) b ( i ) = ∑ f ( k ) b ( i + k ) + 1 \begin{cases} \color{red}a(i)=\sum f(k)a(i+k)+f(0)\\ \color{red}b(i)=\sum f(k)b(i+k)+1 \end{cases} {a(i)=f(k)a(i+k)+f(0)b(i)=f(k)b(i+k)+1
  • 注意到 e ( x > n ) = a ( x ) e ( 0 ) + b ( x ) = 0 e(x>n)=a(x)e(0)+b(x)=0 e(x>n)=a(x)e(0)+b(x)=0
  • 我們得到 a ( > n ) = b ( > n ) = 0 a(>n)=b(>n)=0 a(>n)=b(>n)=0
  • 最後答案為 e ( 0 ) = a ( 0 ) e ( 0 ) + b ( 0 ) e(0)=a(0)e(0)+b(0) e(0)=a(0)e(0)+b(0),即:
  • 最終答案為 e ( 0 ) = b ( 0 ) 1 − a ( 0 ) \color{red}e(0)=\frac{b(0)}{1-a(0)} e(0)=1a(0)b(0)

其他的細節可以參考一下程式碼。

核心程式碼

時間複雜度 O ( K 1 K 2 K 3 + n 2 ) O(K_1K_2K_3+n^2) O(K1K2K3+n2)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 550;

double a[MAX];
double b[MAX];
double f[MAX];
int main()
{
    int KASE;
    while(cin >> KASE){
        while(KASE--){
            int n,k1,k2,k3,aa,bb,cc;
            cin >> n >> k1 >> k2 >> k3 >> aa >> bb >> cc;
            memset(a,0,sizeof(a));
            memset(b,0,sizeof(b));
            memset(f,0,sizeof(f));

            for(int i = 1;i <= k1;++i)
            for(int j = 1;j <= k2;++j)
            for(int k = 1;k <= k3;++k)		/// 注意算 f(x)時候不要把歸零的答案算進去了
                if(i != aa || j != bb || k != cc)f[i+j+k] += 1.0 / k1 / k2 / k3;

            double f0 = 1.0 / k1 / k2 / k3;
            for(int i = n;i >= 0;--i){
                double t1 = f0,t2 = 1.0;
                for(int k = 3;i + k <= n;++k){
                    t1 += f[k] * a[i + k];
                    t2 += f[k] * b[i + k];
                }
                a[i] = t1;
                b[i] = t2;
            }
            printf("%.15f\n",b[0] / (1.0 - a[0]));
        }
    }
    return 0;
}

相關文章