题意:有 n 人排成一队要在官网上激活游戏,(其中 Tomato 的最初排名为第 m 个),
对于队列中的第一个人,在激活的时候有以下四种情况:
1.激活失败:留在队列中继续等待下一次激活 (概率为p1)
2.失去连接:激活失败,并且出队列然后排到队列的尾部 (概率为p2)
3.激活成功:出队列 (概率为p3)
4.服务器瘫痪:所有人都无法激活了 (概率为p4)
求服务器瘫痪时Tomato的排名<=k的概率。
分析:一开始没做出来,推出转移方程了,但下一步就无从下手了,看了题解才明白,还是做题太少缺少经验 !
设 dp[ i ][ j ] 表示 i 个人排队,Tomato 处于第 j 位满足所求结果的概率,则我们所求结果为 dp[ n ][ m ] ,所有情况可以分成三种:
①:
②:
③:
令
将上三式移项整理可得:
①:
②:
③:
撇开第一项,这是一个顺推式,而在推dp[ i ][ ] 的时候,dp[ i-1 ] [ ] 已经被推出来了,可以当作常数项,所以上三式又可以转化为:
①:
②:
③:
到现在的唯一剩下的点就是得解出 dp[ i ][ 1 ] 才能推出后面的。而 dp[ i ][ 1 ] 又要用到 dp[ i ][ i ]。这里可以用消元法求,具体
...
这个方程组的解应该不难想:
倒数第二项等号左右都乘以p,
倒数第三项等号左右都乘以p^2
...
倒数第一项等号左右都乘以p^i
然后再把所有式子等号左右分别相加,最后移项化简答案就呼之欲出了。。
题目给的空间不够大,直接开 dp[ MAXN ][ MAXN ]会超内存,需要用滚动数组优化一下。
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 2005;
const double eps = 1e-5;
double dp[2][N],fac[N];
double c[N];
int main()
{
int n,m,k;
double p1,p2,p3,p4;
while(~scanf("%d",&n))
{
scanf("%d%d %lf%lf%lf%lf",&m,&k,&p1,&p2,&p3,&p4);
if(p1+p2==1.0) //避免后面除零
{
puts("0.00000");
continue;
}
double p = p2/(1-p1);
double p31 = p3/(1-p1);
double p41 = p4/(1-p1);
fac[0]=1.0;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*p;
dp[1&1][1]=p4/(1-p1-p2);
c[1]=p41;
for(int i=2;i<=n;i++)
{
for(int j=2;j<=k;j++)
c[j]=p31*dp[(i-1)&1][j-1]+p41;
for(int j=k+1;j<=i;j++)
c[j]=p31*dp[(i-1)&1][j-1];
double tmp=0.0;
for(int j=i;j>0;j--)
tmp+=c[j]*fac[i-j];
dp[i&1][i]=tmp/(1-fac[i]);
dp[i&1][1]=p*dp[i&1][i]+p41;
for(int j=2;j<i;j++)
dp[i&1][j]=p*dp[i&1][j-1]+c[j];
}
printf("%.5lf\n",dp[n&1][m]);
}
return 0;
}