概率dp
期望的定义
一个期望 e[x] 表示对于一个状态其达到目标状态的平均值(姑且这样简单的认为)
基本套路
例如我们要求一个状态i
例题
Collecting Bugs
一个人受雇于某公司要找出某个软件的bugs和subcomponents,这个软件一共有n个bugs和s个subcomponents,每次他都能同时随机发现1个bug和1个subcomponent,问他找到所有的bugs和subcomponents的期望次数。(s<=1000,n<=1000)
思路
我们发现dp的最终的状态为dp[s][n]。
dp[i][j]=dp[i−1][j]∗(s−i+1)/s∗j/n+dp[i][j−1]∗i/s∗(n−j+1)/n+dp[i−1][j−1]∗(s−i+1)/s∗(n−j+1)/n+dp[i][j]∗i/s∗j/n
由于正着写有些麻烦我们可以反过来写。
用E(i,j)表示他找到了i个bugs和j个subcomponents,表示离找到n个bugs和s个subcomponents还需要的期望次数。
int solve(){
int n,s;
while(~scanf("%d%d",&n,&s)){
memset(e,0,sizeof(e));
double f=1.0*n*s;
for(int i=n;i>=0;i--)
for(int j=s;j>=0;j--){
if(i==n&&j==s)continue;
double p=1,p0=1-i*j/f;
p+=e[i][j+1]*i*(s-j)/f;
p+=e[i+1][j]*j*(n-i)/f;
p+=e[i+1][j+1]*(n-i)*(s-j)/f;
e[i][j]=p/p0;
}printf("%.4lf\n",e[0][0]);
}
}
One Person Game
有三个骰子,分别有 k1,k2,k3个 面。每次掷骰子,如果三个面分别为 a,b,c 则分数重置为 0 ,否则加上三个骰子的分数之和。当分数大于
n 时结束。求游戏的期望步数。(初始分数为 0,k<6 )思路
对于每一个 e[i] = ∑(e[i+k]∗pk)+e[0]∗p0+1
但是我们发现每一项都有p[0]那么我们一定可以通过迭代得到一个只有p[0]的方程
设 e[i]=A[i]∗e[0]+B[i]
= ∑(pk∗A[i+k]∗e[0]+pk∗B[i+k]) + e[0]∗p0+1
= ∑(pk∗A[i+k])+p0)∗e[0] + ∑(pk∗B[i+k])+1
A[i] = ∑(pk∗A[i+k])+p0)
B[i] = ∑(pk∗B[i+k])+1
那么 e[0]=B[0]/(1−A[0])
const int M=1e3+5;
double A[M],B[M],p[M];
void solve(){
int n,k1,k2,k3,c1,c2,c3;
memset(A,0,sizeof(A));memset(B,0,sizeof(B));memset(p,0,sizeof(p));
scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&c1,&c2,&c3);
p[0]=1.0/k1/k2/k3;
for(int a=1;a<=k1;a++)
for(int b=1;b<=k2;b++)
for(int c=1;c<=k3;c++)
if(a!=c1||b!=c2||c!=c3)
p[a+b+c]+=p[0];//先对于每一个k求出它的p[k]
for(int i=n;i>=0;i--){
A[i]=p[0];B[i]=1;
for(int j=3;j<=k1+k2+k3;j++)
A[i]+=A[i+j]*p[j],
B[i]+=B[i+j]*p[j];
}
printf("%.15lf\n",B[0]/(1-A[0]));
}
Activation
有n个人排队等着在官网上激活游戏。Tomato排在第m个。
对于队列中的第一个人。有一下情况:
1、激活失败,留在队列中等待下一次激活(概率为p1)
2、失去连接,出队列,然后排在队列的最后(概率为p2)
3、激活成功,离开队列(概率为p3)
4、服务器瘫痪,服务器停止激活,所有人都无法激活了(概率为p1)。
求服务器瘫痪时Tomato在队列中且的位置<=k的概率思路
<1> e[s][1]=p2∗e[s][s]+p4;
<2> e[s][r<=k]∗(1−p1)=p2∗e[s][r−1]+p3∗e[s−1][r−1]+p4;
<3> e[s][r>k]∗(1−p1)=p2∗e[s][r−1]+p3∗e[s−1][r−1];
但是我们发现对于 e[s][1] 我们必须先求出 e[s][s]
对于 e[s][1] 我们又必须先求出 e[s][s−1]
对于这样的的线性成环的e的关系
我们只能一个一个迭代最后求出e[i][i]之后再得到其他答案
简化一下得
<1> e[i][1]=p2∗e[i][i]+c[1];
<2> e[i][j]=p2∗e[i][j−1]+c[j]+p4;
<3> e[i][j]=p2∗e[i][j−1]+c[j];
e[i][2]<−e[i][1]<−e[i][i]<−e[i][i−1]形成环
先求出e[i][i]即可
e[i][i]=c[i]+e[i][i−1]
e[i][1]=c[1]+p2∗e[i][i]
e[i][2]=c[2]+p2∗(c[1]+e[i][1])c[1]乘了p2(i−1)
e[i][2]=c[2]+p2∗c[1]+p2∗e[i][1]
e[i][2]=c[2]+p2∗(c[1]+p2∗e[i][i])
e[i][2]=c[1]∗p2+c[2]+p2∗p2∗e[i][i]
……
于是得到了 e[i][i]=∑ix=1(c[x]∗p[i−x])+e[i][i]∗p[i](p[x]表示p2x)
void solve(){
if(p4<EPS){puts("0.00000");return;}
p2/=(1-p1),p4/=(1-p1),p3/=(1-p1);
//方便起见我们直接把p1移项。
p[0]=1.0;//p[i]=p2^i
for(int i=1;i<=n;i++)p[i]=p2*p[i-1];
e[1][1]=p4/(1-p2);//e[1][1]=p2*e[1][1]+p4;
c[1]=p4;
bool cur=1;
for(int i=2;i<=n;i++){
cur=!cur;
//预处理所有的常数
for(int j=2;j<=i;j++){
c[j]=p3*e[!cur][j-1];
if(j<=k)c[j]+=p4;
}
e[cur][i]=0;//先求出e[i][i]
for(int j=1;j<=i;j++)e[cur][i]+=c[j]*p[i-j];
e[cur][i]/=(1-p[i]);
e[cur][1]=p2*e[cur][i]+c[1];//得到e[i][1]
for(int j=2;j<i;j++)e[cur][j]=p2*e[cur][j-1]+c[j];//迭代即可
}
printf("%.5lf\n",e[cur][m]);
}
Maze
有n个房间,由n-1条隧道连通起来,实际上就形成了一棵树,
从结点1出发,开始走,在每个结点i都有3种可能:
1.被杀死,回到结点1处(概率为ki)
2.找到出口,走出迷宫 (概率为ei)
3.和该点相连有m条边,随机走一条
求:走出迷宫所要走的边数的期望值。思路
e[i]=k[i]∗e[1]+res/m∗e[f]+res/m∗∑e[son]+res
于是我们用A[i]记录e[1]的系数,B[i]记录e[f]的系数,C[i]记录常数即可
void dfs(int x,int pre){
double res=1-e[x]-k[x],self=0,q;
int m=G[x].size();
q=res/m;
A[x]=k[x];
B[x]=q;
C[x]=res;
for(int i=0;i<m&&res>EPS;i++){
int y=G[x][i];
if(y==pre)continue;
dfs(y,x);
A[x]+=q*A[y];
C[x]+=q*C[y];
self+=q*B[y];
}
A[x]/=1-self;
B[x]/=1-self;
C[x]/=1-self;
}
int main(){
scanf("%d",&T);
for(int cas=1;cas<=T;cas++){
scanf("%d",&n);
int x,y;
for(int i=0;i<M;i++)G[i].clear();
for(int i=1;i<n;i++){
scanf("%d %d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
for(int i=1;i<=n;i++){
scanf("%d %d",&x,&y);
k[i]=0.01*x;e[i]=0.01*y;
}dfs(1,0);
cout<<"Case "<<cas<<": ";
if(1-A[1]<EPS)puts("impossible");
else printf("%.6f\n",C[1]/(1-A[1]));
}
}