1. 程式人生 > 其它 >[HNOI2015] 亞瑟王 題解

[HNOI2015] 亞瑟王 題解

概率期望 DP

Statement

[HNOI2015]亞瑟王

給定一排 \(n\) 個數 \(d_i\),每一個數有 \(p_i\) 的概率選中它,每個數只能選一次。規定一輪遊戲從第一個數開始嘗試選擇,每當你選中一個數 ,將你的分數加上 \(d_i\),結束這輪遊戲。你一共可以進行 \(r\) 輪這樣的遊戲,問期望總分數。

\(T\le 444,n\le 220,r\le 132,0<p_i<1,0\le d_i\le1000\)

Solution

naive 想法 1:按照需要什麼設什麼的套路,直接設 \(dp[i][j][0/1]\) 表示進行了 \(i\) 輪遊戲,第 \(j\) 個數選沒有選的期望分數。但是問題在於選擇 \(i\)

輪第 \(j\) 張牌的時候,需要前 \(i-1\) 輪中沒有用過 \(j\) ,且 \(i\) 輪前 \(j-1\) 張牌全部 \(miss\)

naive 想法2:

碰到有些期望的題目,一定要先秉承著白嫖的思路,在貢獻具有獨立性的時候,能拆則拆

——wyb

發現每一張牌的貢獻是獨立的,依據期望的線性性,所以可以直接算每一張牌發生貢獻的概率。

於是依然按照需要什麼設什麼的想法,設 \(dp[i][j]\) 表示前 \(i\) 輪,選中第 \(j\) 個數的概率

不假思索地,我們得到這樣的轉移式子:

\[dp[i][j]=dp[i-1][j]+(1-dp[i-1][j])\times p[j]\times miss \]

其中 \(miss\)

表示在 \(i\) 輪中,前 \(j\) 張牌全都 在之前選過了/這次沒有選中,所以

\[miss*=dp[i-1][j]+(1-dp[i-1][j])*(1-p[j]) \]

在轉移 \(dp[i]\) 的時候順帶著轉移即可。

但是這樣實際上也是錯的,考慮把 \(miss\) 這個乘法式子拆開,發現存在長成 \(dp[i-1][j]\times dp[i-1][j-1]\) 之類的項,就違反了一輪只能選中一個的規則,GG

正解:

好的,不會做,去看題解

發現思維一直禁錮在一個奇怪的位置,就是必須要記錄當前是幾輪

但其實仔細思考一下發現記錄這個輪數是完全沒有必要的,因為對於一個 $i $ 而言,我關注的是他前面有幾個數被抽了,而並不關注具體是在那些輪數被選擇的

(很可惜,在看了題解的狀態後自己推式子的時候並沒有擺脫上面所述的這樣一個思維的禁錮)

在沒有限制的條件下,也就是假如即使選中了一個數也可以繼續選,那麼顯然選中第 \(i\) 的概率就是 \(\sum_{i=0}^{r-1}(1-p)^ip=1-(1-p)^r\)

但是顯然沒有這麼美好,我們顯然需要在前面填一個係數

\(f[i][j]\) 表示 \(r\) 輪結束後,第 \(i\) 個數有 \(j\) 次選擇機會的概率,那麼式子變成了

\(\sum_{k=1}^{r} f[i][k](1-(1-p_i)^k)\)

發現這個表述有點蠢,選擇機會意義不明,所以正如前面所言,”對於一個 $i $ 而言,我關注的是他前面有幾個數被抽了“,那麼設 \(dp[i][j]\) 表示 \(r\) 輪結束後,前 \(i\) 個數中,抽走 \(j\) 個的概率。

容易發現現在要計算的變成了 \(\sum_{k=0}^{r-1}dp[i-1][k](1-(1-p_i)^{r-k})\)

考慮 DP 如何轉移:

\[dp[i][j]= \begin{cases} \ dp[i-1][j]\times (1-p_i)^{r-j}\\ \ dp[i-1][j-1]\times (1-(1-p_i)^{r-j+1}) \end{cases} \]

時刻注意 \(dp\) 狀態的意義是在 \(r\) 輪結束之後

複雜度 \(O(tnr)\)

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 250;
const int M = 150;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
    int s=0,w=1; char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}

double p[N],fp[N];
double pw[N][N],dp[N][N];
int d[N];
int T,n,r;

signed main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&r);
        memset(fp,0,sizeof(fp));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;++i)
            scanf("%lf%d",&p[i],&d[i]);
        for(int i=1;pw[i][0]=1,i<=n;++i)
            for(int j=1;j<=r;++j)
                pw[i][j]=pw[i][j-1]*(1-p[i]);
        dp[1][0]=pw[1][r],dp[1][1]=fp[1]=1-dp[1][0];
        for(int i=2;i<=n;++i)
            for(int j=0;j<=r;++j){
                fp[i]+=dp[i-1][j]*(1-pw[i][r-j]);
                dp[i][j]+=dp[i-1][j]*pw[i][r-j];
                if(j)dp[i][j]+=dp[i-1][j-1]*(1-pw[i][r-j+1]);
            }
        double ans=0;
        for(int i=1;i<=n;++i)
            ans+=fp[i]*d[i];
        printf("%.10lf\n",ans);
    }
    return 0;
}