【JZOJ 3072】掷骰子

来源:互联网 发布:免费听音乐的软件 编辑:程序博客网 时间:2024/05/16 12:40

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格.接下来是太郎掷骰子,太郎想知道他赢得比赛的概率就多少。

Solution

用DP,
因为正着推不好玩,所以考虑一下倒推,
fi,j表示两人分别在i和j,到i走,从终点开始倒推,

很显然,如果i=n,f直接加1,j=n直接跳过;
再分类讨论:

1、两人都在[n-m+1,n]这个区间内,i赢的概率是:

1m+(m1m)21m+(m1m)41m+(m1m)61m...

i一次跳到了终点,或者第一次没中,j也没中,i才中,以此类推,

=1mi=0(m1m)2i

用等比数列表示:
=1m(m1m)11m22m

由于(m1m)无限接近0,也就是等于0,所以
=1mm22m1=m2m1

所以:
fi,j=m2m1

2、i在[n-m+1,n]这个区间内:

fi,j=1m+j+mk=j+1(m1)fi,km2

有可能直接跳到终点,或者从其他点跳来,对于每个的k,都有(m-1)个非终点跳到i,同时每个k刚好跳到j和每个非终点的跳到i概率积为m2

3、j在[n-m+1,n]这个区间内:

fi,j=(i+n=m?1:0)m2+i+mk=i+1(m1)fk,jm2

如果i在n-m这个位置,那么就有1m的概率直接跳到终点,但也有可能在跳之前j先到了终点,所以要减m1m1m等于1m2
后面的与前面的差不多,这里就不多写了;

4、i,j都不在区间内:

这个简单嘛,直接把他们后面的概率加起来除m2即可,
fi,j=i+mk=i+1j+ml=j+1fj,km2


我们发现每个可以写一个矩阵后缀和来优化,
答案就是fx,y

复杂度:O(n2)

Code

成就:代码写进700!

#include<cstdio>#include<cstdlib>#define fod(i,a,b) for(int i=a;i>=b;i--)using namespace std;typedef double db;const int N=2050;int m,n,x,y;db f[N][N];db S(int q,int w,int q1,int w1){return f[q][w]-f[q1][w]-f[q][w1]+f[q1][w1];}int main(){    scanf("%d%d%d%d",&n,&m,&x,&y);    db q=(1.0*m/(2*m-1));    fod(i,n,1)        fod(j,n,1)        {            f[i][j]=f[i+1][j]+f[i][j+1]-f[i+1][j+1];            if(i==n){f[i][j]++;continue;}            if(j==n)continue;            if(i+m>n&&j+m>n)f[i][j]+=q;                else if(i+m>n)f[i][j]+=((m-1)*S(i,j+1,i+1,j+m+1)+m)/(m*m);                    else if(j+m>n)f[i][j]+=((m-1)*S(i+1,j,i+m+1,j+1)+(i+m==n))/(m*m);                        else f[i][j]+=S(i+1,j+1,i+m+1,j+m+1)/(m*m);        }    printf("%.6lf\n",S(x,y,x+1,y+1));    return 0;}
0 0