HDU 4089 Activation 概率DP
题目链接:[http://acm.hdu.edu.cn/showproblem.php?pid=4089]
题目大意:
Tomato去排队激活游戏,每次对队首位置的人进行处理,处理时会产生四种情况:
- 激活失败,队伍不变,下次重新从队首的人开始处理,概率为p1
- 连接失败,队首的人排到队尾,下次从新的队首的人开始处理,概率为p2
- 激活成功,队首的人退出队伍,下次从队列中下一个人开始处理,概率为p3
- 服务器崩溃,GG,概率为p4
已知队列总人数为n,Tomato当前在队列中的位置为m,问当服务器崩溃时Tomato在队列中且当前位置序号小于等于k的概率是多少。
分析:
一道求数学期望的题目,利用概率DP,首先找出可行的状态转移方程,DP[ i ][ j ]表示当前队列中有 i 个人,Tomato排在 j 号位,当前情况下符合条件的概率为多少。
对于排在队列中的不同的位置时,有不同的表示:
j==1:dp[ i ][ j ] = p1 * dp[ i ][ j ] + p2 * dp[ i ][ i ] + p4
2<=j<=k:dp[ i ][ j ] = p1 * dp[ i ][ j ] + p2 * dp[ i ][ j-1 ] + p3 * dp[ i-1 ][ j-1 ] + p4
k<j<=i:dp[ i ][ j ] = p1 * dp[ i ][ j ] + p2 * dp[ i ][ j-1 ] + p3 * dp[ i-1 ][ j-1 ]
将上述表达式简化:
p = p2/(1-p1)
p31 = p3/(1-p1)
p41 = p4/(1-p1)
j==1:dp[ i ][ j ] = p * dp[ i ][ i ] + p41
2<=j<=k:dp[ i ][ j ] = p * dp[ i ][ j-1 ] + p31 * dp[ i-1 ][ j-1 ] + p41
k<j<=i:dp[ i ][ j ] = p * dp[ i ][ j-1 ] + p31 * dp[ i-1 ][ j-1 ]
从 i = 1->n 循环递推算出每一项dp[ i ],此时dp[i - 1]已经在之前的计算中得到了,所以可以把除了dp[ i ][ j∈[1,i] ]之外的所有项归为常数项
即表示为:
j==1:dp[ i ][ j ] = p * dp[ i ][ i ] + C[ j ]
2<=j<=k:dp[ i ][ j ] = p * dp[ i ][ j-1 ] + C[ j ]
k<j<=i:dp[ i ][ j ] = p * dp[ i ][ j-1 ] + C[ j ]
对于每一次循环,首先要得到dp[ i ][ 1 ]的值
dp[ i ][ 1 ]可以用递推公式进行递推得到:
dp[ i ][ 1 ] = p * dp[ i ][ i ] + c[ 1 ]
dp[ i ][ i ] = p * dp[ i ][ i-1 ] + c[ i ]
dp[ i ][ i-1 ] = p * dp[ i ][ i-2 ] + c[ i-1 ]
…
dp[ i ][ 2 ] = p * dp[ i ][ 1 ] + c[ 2 ]
所以整理之后可以用一个式子表达dp[ i ][ 1 ]:
d
p
[
i
]
[
1
]
=
C
[
1
]
+
∑
n
=
1
i
p
n
⋅
C
[
i
+
1
−
n
]
1
−
p
i
dp[i][1] = \frac{C[1]+\sum_{n=1}^{i}p^n\cdot C[i+1-n]}{1-p^i}
dp[i][1]=1−piC[1]+∑n=1ipn⋅C[i+1−n]
然后通过迭代把dp[ i ][ j∈[1,i ] ]都递推出来
注意特判一下p4<eps的情况
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
static const int maxn = 2000;
static const double eps = 1e-5;
double p1,p2,p3,p4;
int n,m,k;
double dp[maxn][maxn];
double C[maxn];
int main(){
while(scanf("%d %d %d %lf %lf %lf %lf",&n,&m,&k,&p1,&p2,&p3,&p4)!=EOF){
if(p4<eps){
printf("0.00000\n");
continue;
}
double p = p2/(1-p1);
double p31 = p3/(1-p1);
double p41 = p4/(1-p1);
//dp[1][1] = p41/(1-p);
for(int i=1/*2*/;i<=n;i++){
C[1] = p41;
for(int j=2;j<=k&&j<=i;j++)
C[j] = p31 * dp[i-1][j-1] + p41;
for(int j=k+1;j<=i;j++)
C[j] = p31 * dp[i-1][j-1];
double tmp = C[1];
for(int j=2;j<=i;j++)
tmp+=C[j]*pow(p,i+1-j);
dp[i][1] = tmp/(1-pow(p,i));
for(int j=2;j<=i;j++)
dp[i][j] = p * dp[i][j-1] + C[j];
}
printf("%.5f\n",dp[n][m]);
}
return 0;
}
数组再大一点可能就会爆M 可以用滚动数组来代替
参考链接:[https://www.cnblogs.com/kuangbin/archive/2012/10/03/2710987.html]