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在两步之前就到达了
那么,剩下的就是转移了。
我们分类讨论。
Case 1 : i<=n-m,j<=n-m
这部分没什么限制,直接dp
Case 2:i>n-m,j>n-m
他们一旦走超过n-m,因为有反弹,他们的位置始终会在区间
[n−m+1,n]
内。
且除非有一个人走到了
n
,他们可能无穷地走下去。
这段区间内,所有点走到终点的概率都是
那A先走且获胜的概率就是
这就是个等比数列嘛,上求和公式
中间过程自己化简,最后就是
Case 3 : i>n-m,j<=n-m
i
只有1种可能上次从n转移过来,
所以
Case 4 : i<=n-m,j>n-m
类似Case3
若 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=2010;
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*1.0/(m+m-1);
fd(i,n,1)
fd(j,n,1)
{
sum[i][j]=sum[i+1][j]+sum[i][j+1]-sum[i+1][j+1];
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+1,i+1,j+m+1)*(m-1)/(m*m)+1.0/m;
else
if(j>n-m) sum[i][j]+=(S(i+1,j,i+m+1,j+1)*(m-1)+(i==n-m))/(m*m);
else sum[i][j]+=S(i+1,j+1,i+m+1,j+m+1)/(m*m);
}
printf("%.6lf",S(x,y,x+1,y+1));
return 0;
}