1. 程式人生 > >【[JLOI2013]卡牌遊戲】

【[JLOI2013]卡牌遊戲】

思路太妙了

剛開始yy出了一種比較自然的dp方法,就是按照遊戲的進行來開始dp,設\(dp[i][j]\)表示第\(i\)個人為莊家,還剩下\(j\)個人的概率為多少,但是很快發現這個樣子沒法轉移,因為沒有辦法確定下一個莊家是誰

於是只能將第二維壓成一個狀態\(s\) ,\(dp[i][s]\)表示第\(i\)個人為莊家存活狀態為\(s\)的概率為多少,顯然這個樣子做一個狀壓dp的話我們可以列舉當前的莊家,當前的狀態,以及當前的莊家用的牌是哪一張,之後我們就可以確定下一個莊家是誰,這樣就可以轉移了

但是這個複雜度大概是\(O(nm2^n)\),30%的資料應該還是能過的

正解的思路就相當秒了,我們設\(dp[i][j]\)

表示有\(i\)個人參與遊戲,從莊家(即1)數\(j\)個人獲勝的概率是多少

初始狀態\(dp[1][1]=1\)

之後我們列舉\(i,j\),之後繼續列舉\(k\)表示莊家(即1)選擇了第\(k\)張牌,由於我們這裡的莊家都是1所以我們要通過模擬第一步進行狀態的轉移

我們的到第一步之後就可以轉化為\(dp[i-1][]\)

我們枚舉了\(k\)自然就可以得到第一輪被淘汰的人\(p\)

如果\(p=j\)\(j\)上來就被淘汰就不用轉移了

而第 \(p\)個人被淘汰之後,剩下的 \(i-1\) 個人要組成一個新的環,莊家為第 \(p\)個人的下一個。容易算出,當 \(p>j\)

時,第 \(j\)個人是新的環裡從新莊家數起的第 \(i-p+j\) 個人,當 \(c<j\) 時,第 \(j\) 個人是新的環裡從新莊家數起的第 \(j-p\)個人。

程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 55
int n,m;
int a[maxn];
double dp[maxn][maxn];
inline int read()
{
    char c=getchar();
    int x=0;
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9')
      x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x;
}
int main()
{
    n=read(),m=read();
    for(re int i=1;i<=m;i++)
        a[i]=read();
    dp[1][1]=1.0;
    for(re int i=2;i<=n;i++)
    {
        for(re int j=1;j<=i;j++)
        {
            for(re int k=1;k<=m;k++)
            {
                int p=a[k]%i;
                if(!p) p=i;
                if(p>j) dp[i][j]+=dp[i-1][i-p+j]/m;
                if(p<j) dp[i][j]+=dp[i-1][j-p]/m;
            }
        }
    }
    for(re int i=1;i<=n;i++)
    printf("%.2lf",dp[n][i]*100),putchar('%'),putchar(' ');
    return 0;
}