天天看點

【JZOJ 3072】 擲骰子DescriptionAnalysisCode

Description

太郎和一隻免子正在玩一個擲骰子遊戲。有一個有N個格子的長條棋盤,太郎和兔子輪流擲一個有M面的骰子,骰子M面分别是1到M的數字.且擲到任意一面的機率是相同的.擲到幾.就往前走幾步.當誰走到第N格時,誰就獲勝了。遊戲中還有一個規則“反彈”.就是當一位選手要走到第N格外時.他就會後退(就像飛行棋進營一樣)。

假設現在一位追手在A格.當他擲出B時:

1.A+B<N,走到第A+B格,

2.A+B=N,走到第N格,獲勝。

3.A+B≥N,走到第(N-(A+B-N)格

現在太郎和兔子分别在第x和y格.接下來是太郎擲骰子,太郎想知道他赢得比賽的機率就多少。
           

100%的資料.10≤n≤ 2000,1≤m,x,y≤n-1

Analysis

很好的一道題,2^1024個好評,推薦大家做做。

初二一群人水法水過,暴力走15000步遞推算答案卡精度卡時間竟然給卡過了?!無力吐槽。(infleaking同學(不是vfleaking神犇啊(%%%),大家不要看錯了)還高舉“能過的方法就是好方法”、“水法也是好方法”的旗幟。。。)

好了,扯了這麼多,這些個人見解到時候在集訓總結裡寫吧,回到正題。

DP

設 f[i][j] 表示A在i,B在j,A先走并獲勝的機率。

邊界的話如果 i=n ,顯然 f[i][j]=1

如果 j=n ,顯然 f[i][j]=0

有人要問了,如果 i=j=n , f[i][j]=?

看看 f 的定義,由于目前是A走,是以一步之前是B走,兩步之前是A走,是以A在兩步之前就到達了n,是以 f[n][n]=1

那麼,剩下的就是轉移了。

我們分類讨論。

Case 1 : i<=n-m,j<=n-m

這部分沒什麼限制,直接dp

f[i][j]=∑i′=i+1i+m∑j′=j+1j+mf[i′][j′]∗1m2

Case 2:i>n-m,j>n-m

他們一旦走超過n-m,因為有反彈,他們的位置始終會在區間 [n−m+1,n] 内。

且除非有一個人走到了 n ,他們可能無窮地走下去。

這段區間内,所有點走到終點的機率都是1m。

那A先走且獲勝的機率就是

limn→+∞∑i=0n(1−1m)2i∗1m

這就是個等比數列嘛,上求和公式

=1m∗1∗(1−1m)+∞1−(1−1m)2

中間過程自己化簡,最後就是

=m2m−1

Case 3 : i>n-m,j<=n-m

i 隻有1種可能上次從n轉移過來,i共有m種可能,同時 j 也有m種取值,總共有 m2 種可能情況,區間内所有點都可視為一樣的。

是以

f[i][j]=(m−1)∗(∑j+mj′=j+1f[i][j′])+mm2

Case 4 : i<=n-m,j>n-m

類似Case3

f[i][j]=(m−1)∗(∑i+mi′=i+1f[i′][j])+(i==n−m)m2

若 i=n−m ,當 i′=j=n 時機率為1

當然,dp可以累加到二維字首和上,這樣單次狀态轉移就不需要 O(n2) 的時間。

Code

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
using namespace std;
typedef double db;
const int N=;
db sum[N][N];
db S(int x1,int y1,int x2,int y2)
{
    return sum[x1][y1]-sum[x1][y2]-sum[x2][y1]+sum[x2][y2];
}
int main()
{
    int n,m,x,y;
    scanf("%d %d %d %d",&n,&m,&x,&y);
    db t=m*/(m+m-);
    fd(i,n,)
        fd(j,n,)
        {
            sum[i][j]=sum[i+][j]+sum[i][j+]-sum[i+][j+];
            if(i==n)
            {
                sum[i][j]++;
                continue;
            }
            if(j==n) continue;
            if(i>n-m && j>n-m) sum[i][j]+=t;
            else
            if(i>n-m) sum[i][j]+=S(i,j+,i+,j+m+)*(m-)/(m*m)+/m;
            else
            if(j>n-m) sum[i][j]+=(S(i+,j,i+m+,j+)*(m-)+(i==n-m))/(m*m);
            else sum[i][j]+=S(i+,j+,i+m+,j+m+)/(m*m);
        }
    printf("%.6lf",S(x,y,x+,y+));
    return ;
}