title: 期望习题(概率dp)
date: 2019-08-13 10:57:10
tags: 概率
几道例题
1.Collecting Bugs POJ-2096(期望DP)
题意:有n种bug,s个系统会出现bug,bug数量可以是无穷多的,有一个人一天能够找一个bug,bug出现在各个系统是等概率的,系统中发现的bug也是等概率的属于每种类别,问找到每种bug至少找到一个,每个系统至少找到一个bug的时间期望.
题解:由题意可知,一个bug出现在某个系统的概率是 1 s \frac{1}{s} s1,属于某个类型的概率是 1 n \frac{1}{n} n1,
设dp[i] [j]表示已经在i个系统中找到了 j种bug,距离全部找到(即达到目标状态)还需要天数的期望,所以有dp[s] [n]=0,本题采用逆推求期望,最终要求的即为dp[0] [0].
所以dp[i] [j]可由以下状态推得:
-
dp[i] [j] —>旧系统,旧bug(即这个bug是在已经找到过bug的系统中再找到的,并且已经找到过了它同种类的bug已经被找到过了)
P 1 = i s ∗ j n P_1=\frac{i}{s}*\frac{j}{n} P1=si∗nj
-
dp[i+1] [j] —>新系统,旧bug
P 2 = ( s − i ) s ∗ j n P_2=\frac{\left( s-i \right)}{s}*\frac{j}{n} P2=s(s−i)∗nj
-
dp[i] [j+1]—>旧系统,新bug
P 3 = i s ∗ ( n − j ) n P_3=\frac{i}{s}*\frac{\left( n-j \right)}{n} P3=si∗n(n−j)
-
dp[i+1] [j+1]—>新系统,新bug
P 4 = ( s − i ) s ∗ ( n − j ) n P_4=\frac{\left( s-i \right)}{s}*\frac{\left( n-j \right)}{n} P4=s(s−i)∗n(n−j)
所以有:dp[i] [j]= P 1 P_1 P1 *dp[i] [j] + P 2 P_2 P2 *dp[i+1] [j]+ P 3 P_3 P3 *dp[i] [j+1]+ P 4 P_4 P4 *dp[i+1 [j+1]+1
化简得: d p [ i ] [ j ] = ( s − i ) ∗ j ∗ d p [ i + 1 ] [ j ] + i ∗ ( n − j ) ∗ d p [ i ] [ j + 1 ] + ( s − i ) ( n − j ) ∗ d p [ i + 1 ] [ j + 1 ] + n s s n − i j dp\left[ i \right] \left[ j \right] =\frac{\left( s-i \right) *j*dp\left[ i+1 \right] \left[ j \right] +i*\left( n-j \right) *dp\left[ i \right] \left[ j+1 \right] +\left( s-i \right) \left( n-j \right) *dp\left[ i+1 \right] \left[ j+1 \right] +ns}{sn-ij} dp[i][j]=sn−ij(s−i)∗j∗dp[i+1][j]+i∗(n−j)∗dp[i][j+1]+(s−i)(n−j)∗dp[i+1][j+1]+ns
#include <iostream>
#include <map>
#include <algorithm>
#include <cstring>
#include <queue>
const int maxn=1005;
using namespace std;
double dp[maxn][maxn];
int main()
{
int n,s;
scanf("%d%d",&n,&s);
dp[s][n]=0.0;
for(int i=s;i>=0;i--)
{
for(int j=n;j>=0;j--)
{
if(i==s&&j==n)
continue;
dp[i][j]=((s-i)*j*dp[i+1][j]+i*(n-j)*dp[i][j+1]+(s-i)*(n-j)*dp[i+1][j+1]+n*s)*1.0/(s*n-i*j);
//cout<<dp[i][j]<<"**";
}
}
printf("%.4lf\n",dp[0][0]);
return 0;
}
2.Race to 1 Again LightOJ - 1038 (期望DP)
题意:给出一个数D,在1~D中选一个它的因子,让D除以这个因子得到一个新的D,这个新的D再选一个因子继续除,直到D=1,求进行的除法次数的期望
题解:设dp[i]表示由i变成1的除法次数期望,易知dp[1]=0
设j是i的一个因子,dp[i]由dp[j]转移而来
d p [ i ] = Σ d p [ j ] n u m + 1 dp\left[ i \right] =\frac{\varSigma dp\left[ j \right]}{num}+1 dp[i]=numΣdp[j]+1(设i=k*j,k为选择的因子,1≤k≤i,num表示i的因子个数)
当k=1时, d p [ i ] = d p [ i ] + Σ d p [ j ] n u m + 1 dp\left[ i \right] =\frac{dp\left[ i \right] +\varSigma dp\left[ j \right]}{num}+1 dp[i]=numdp[i]+Σdp[j]+1(j<i)dp[i]
化简可以得到 d p [ i ] = n u m + Σ d p [ j ] n u m − 1 dp\left[ i \right] =\frac{num+\varSigma dp\left[ j \right]}{num-1} dp[i]=num−1num+Σdp[j](j<i)
dp[2]=(2+dp[1])/1=2
#include <iostream>
#include <map>
#include <algorithm>
#include <cstring>
#include <queue>
const int maxn=10e5+5;
using namespace std;
double dp[maxn];
int main()
{
int num,j,n,t;
dp[1]=0.0,dp[2]=2.0;
for(int i=3;i<=100000;i++)
{
num=0;
for(j=2;j*j<i;j++)
{
if(i%j==0)
{
dp[i]+=dp[j]+dp[i/j];
num+=2;
}
}
if(j*j==i)
{
num++;
dp[i]+=dp[j];
}
num+=2;//记得另外算上除以因子1和i本身的情况
dp[i]=(num+dp[i])/(num-1);
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
printf("Case %d: %lf\n",i,dp[t]);
}
return 0;
}
3.Dice (III) LightOJ - 1248 (期望+几何分布)
题意:给你一个有n个面的骰子,问你这n面都出现过的投掷次数的期望
题解:设dp[i]为扔出i个不同面后,到达目标n还需要的投掷次数的期望,dp[n]=0;
每次投掷其实只有两种情况:出现过的 P 1 = i n P_1=\frac{i}{n} P1=ni和 没出现过的 P 2 = n − i n P_2=\frac{n-i}{n} P2=nn−i
所以有: d p [ i ] = i n d p [ i ] + n − i n d p [ i + 1 ] + 1 dp\left[ i \right] =\frac{i}{n}dp\left[ i \right] +\frac{n-i}{n}dp\left[ i+1 \right] +1 dp[i]=nidp[i]+nn−idp[i+1]+1
化简得: d p [ i ] = d p [ i + 1 ] + n n − i dp\left[ i \right] =dp\left[ i+1 \right] +\frac{n}{n-i} dp[i]=dp[i+1]+n−in
#include <iostream>
#include <map>
#include <algorithm>
#include <cstring>
#include <queue>
const int maxn=10e5+5;
using namespace std;
double dp[maxn];
int main()
{
int t;
cin>>t;
for(int i=1;i<=t;i++)
{
int n;
cin>>n;
dp[n]=0;
for(int j=n-1;j>=0;j--)
dp[j]=dp[j+1]+n*1.0/(n-j);
printf("Case %d: %.7lf\n",i,dp[0]);
}
return 0;
}
4.Activation HDU-4089 (概率DP+方程组)
题意:有n个人在排队注册游戏账号,对于每个用户,都可能遇到以下情况:
- 处理失败,该用户继续停留在原位等待重新注册,发生的概率为 P 1 P_1 P1
- 处理错误,该用户到队尾重新排队,发生的概率为 P 2 P_2 P2
- 处理成功,该用户出队,发生的概率为 P 3 P_3 P3
- 服务器故障,所有用户都不能注册了,发生的概率为 P 4 P_4 P4
Tomato现在排在第m个位置,求他排在第k位以前(包括第k位)时服务器故障的概率
题解:设dp[i] [j]表示队伍人数为i时,Tomato排在第j位的概率,我们最终要求的结果就是dp[n] [m]
状态转移方程:
-
j==1时, d p [ i ] [ 1 ] = P 1 ∗ d p [ i ] [ 1 ] + P 2 ∗ d p [ i ] [ i ] + P 4 dp[i] [1]=P_1*dp[i] [1]+P_2*dp[i] [i]+P_4 dp[i][1]=P1∗dp[i][1]+P2∗dp[i][i]+P4
( P 3 P_3 P3对应的情况:如果Tomato成功,出队了就成了dp[i] [0],而dp[i] [0]=0,就像如果 P 4 P_4 P4=0,即不存在服务器故障情况,输出的概率就为0了)
-
1<j≤k时, d p [ i ] [ j ] = P 1 ∗ d p [ i ] [ j ] + P 2 ∗ d p [ i ] [ j − 1 ] + P 3 ∗ d p [ i − 1 ] [ j − 1 ] + P 4 dp[i] [j]=P_1*dp[i] [j]+P_2*dp[i] [j-1]+P_3*dp[i-1] [j-1]+P_4 dp[i][j]=P1∗dp[i][j]+P2∗dp[i][j−1]+P3∗dp[i−1][j−1]+P4
-
j>k时, d p [ i ] [ j ] = P 1 ∗ d p [ i ] [ j ] + P 2 ∗ d p [ i ] [ j − 1 ] + P 3 ∗ d p [ i − 1 ] [ j − 1 ] dp[i] [j]=P_1*dp[i] [j]+P_2*dp[i] [j-1]+P_3*dp[i-1] [j-1] dp[i][j]=P1∗dp[i][j]+P2∗dp[i][j−1]+P3∗dp[i−1][j−1] (这里不能服务器故障,不然题目要求的事件就不会发生了)
要化简上面式子:
令 x 2 = P 2 1 − P 1 x_2=\frac{P_2}{1-P_1} x2=1−P1P2, x 3 = P 3 1 − P 1 x_3=\frac{P_3}{1-P_1} x3=1−P1P3, x 4 = P 4 1 − P 1 x_4=\frac{P_4}{1-P_1} x4=1−P1P4
如果我们对i从1->n递推的话,dp[i-1] [j-1]相对dp[i] [j-1]就可以看作是已经求解的常数项进行处理,
d p [ i ] [ 1 ] = x 2 ∗ d p [ i ] [ i ] + c [ 1 ] dp[i] [1]=x_2*dp[i] [i]+c[1] dp[i][1]=x2∗dp[i][i]+c[1], c [ 1 ] = x 4 c[1]=x_4 c[1]=x4 (j==1时)
d p [ i ] [ j ] = x 2 ∗ d p [ i ] [ j − 1 ] + c [ j ] dp[i] [j]=x_2*dp[i] [j-1]+c[j] dp[i][j]=x2∗dp[i][j−1]+c[j], c [ j ] = x 3 ∗ d p [ i − 1 ] [ j − 1 ] + x 4 c[j]=x_3* dp[i-1] [j-1]+x_4 c[j]=x3∗dp[i−1][j−1]+x4 (j>k时不加 x 4 x_4 x4)
迭代求解 d p [ i ] [ i ] dp[i][i] dp[i][i]:
求出 d p [ i ] [ i ] dp[i] [i] dp[i][i]后可根据 d p [ i ] [ 1 ] = x 2 ∗ d p [ i ] [ i ] + c [ 1 ] dp[i] [1]=x_2*dp[i][i]+c[1] dp[i][1]=x2∗dp[i][i]+c[1],推出 d p [ i ] [ 1 ] dp[i] [1] dp[i][1],从而推出所有的 d p [ i ] [ j ] dp[i][j] dp[i][j]
注意:要特判p4=0的情况,题目的内存限制很小,32768 kB= 2 25 2^{25} 225Byte,大概double型占8Byte,大概只能开 2 22 2^{22} 222即大概 4 ∗ 1 0 6 4*10^6 4∗106 大小的double型数组,而根据题目 n ∗ m n*m n∗m, d p [ n ] [ m ] dp[n][m] dp[n][m]就已经 4 ∗ 1 0 6 4*10^6 4∗106,何况还有别的数组要占用内存,所以肯定是不够的,又因为只用到 d p [ i − 1 ] [ ] dp[i-1][\ ] dp[i−1][ ]和 d p [ i ] [ ] dp[i][\ ] dp[i][ ]的关系,所以用滚动数组进行处理
#include <iostream>
#include <map>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#include <stdlib.h>
const int maxn=2005;
const double eps=1e-6;
using namespace std;
double dp[maxn][maxn];
int main()
{
int n,m,k;
double p1,p2,p3,p4;
double x2_pow[maxn],c[maxn];
while(scanf("%d%d%d%lf%lf%lf%lf",&n,&m,&k,&p1,&p2,&p3,&p4)==7)
{
if(fabs(p4)<eps)
{
printf("0.00000\n");
continue;
}
double x2=p2/(1-p1);
double x3=p3/(1-p1);
double x4=p4/(1-p1);
x2_pow[0]=1.0;
for(int i=1;i<=n;i++)
x2_pow[i]=x2_pow[i-1]*x2;//求x2的次幂
c[1]=x4;
dp[1][1]=x4/(1-x2);
for(int i=2;i<=n;i++)
{
for(int j=2;j<=k;j++)
c[j]=x3*dp[(i-1)&1][j-1]+x4;
for(int j=k+1;j<=i;j++)
c[j]=x3*dp[(i-1)&1][j-1];//求好c[j]
double sum=0.0;
for(int j=1;j<=i;j++)
sum+=x2_pow[i-j]*c[j];
dp[i&1][i]=sum/(1-x2_pow[i]);
dp[i&1][1]=x2*dp[i&1][i]+c[1];
for(int j=2;j<i;j++)
dp[i&1][j]=x2*dp[i&1][j-1]+c[j];
}
printf("%.5lf\n",dp[n&1][m]);
}
return 0;
}