算法——期望DP
概述
一般来说,概率DP找到正确的状态定义后,转移是比较容易想到的。但状态一定是“可数”的,把有范围的整数作为数组下标。事实上,将问题直接作为状态是最好的。如问“n人做X事的期望次数”,则设计状态为f[i]表示i个人做完事的期望。转移一般是递推,即从上一个状态转移得(填表)或转移向下一个状态(刷表)。
有时期望DP需以最终状态为初始状态转移,即逆推。如f[i]表示期望还要走f[i]步到达终点。这种状态的转移是刷表法,形如,其中p[ ]表示转移的概率,w[ ]表示转移对答案的贡献。一般来说,初始状态确定时可用顺推,终止状态确定时可用逆推。
规律
1.期望可以分解成多个子期望的加权和,权为子期望发生的概率,即
;
2.期望从后往前找,一般是答案;
3.解决过程,找出各种情况乘上这种情况发生的概率,求和;
4.概率DP一定要初始化!
全概率公式
公式表示若事件A1,A2,…,An构成一个完备事件组且都有正概率,则对任意一个事件B都有公式成立。
假如事件A与B相互独立,那么:
例题
1.UVA11021 Tribles麻球繁衍
UVA11021 Tribles麻球繁衍
题意翻译
题目大意
一开始有k种生物,这种生物只能活1天,死的时候有的概率产生ii只这种生物(也只能活一天),询问m天内所有生物都死的概率(包括m天前死亡的情况)
输入格式
第一行输入一个整数T,表示数据总数
每一组先输入三个整数n(1<=n<=1000),k(0<=k<=1000),m(0<=m<=1000)
然后输入n个整数,分别为到
对于每一组数据,先输出"Case #x: " 再输出答案,精度要求在1e-6以内
—————————————————————————————————————————
思路:每个麻球是互相独立的,设计状态f[i]表示一个麻球i天内死绝的概率,则n个麻球在i天内死亡的概率是 。转移时考虑这个麻球第一天繁衍多少个,它们在接下来的i−1天内死绝了。转移方程为 ,
即
其中的意思是,又生了j个麻球,这些麻球在i-1天后死亡。
由于一开始有k只麻球,且各麻球死亡是独立的,所以答案为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e3+7;
const ll mod=1e9+7;
ll t,n,k,cnt,m;
double p[N];
double f[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>t;
while(t--)
{
memset(f,0,sizeof f);
memset(p,0,sizeof p);
cnt++;
cin>>n>>k>>m;
for(int i=0;i<n;i++)
cin>>p[i];
f[1]=p[0];
for(int i=2;i<=m;++i)
for(int j=0;j<n;++j)
f[i]+=pow(f[i-1],j)*p[j];
printf("Case #%lld: %.7lf\n",cnt,pow(f[m],k));
}
}
2.算概率(简单,数论)
算概率
说明:有 1 道题,做对的概率是 在模 意义下为 500000004 )。
表示前 i 道题做对 j道的概率转移时考虑第 j 道题是否做对,转移方程为:
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e3+7;
const ll mod=1e9+7;
ll n,f[N][N],a[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=f[0][0]=1;i<=n;i++)
{
f[i][0]=f[i-1][0]*(mod+1-a[i])%mod;
for(int j=1;j<=i;j++)
f[i][j]=(f[i-1][j]*(mod+1-a[i])+f[i-1][j-1]*a[i])%mod;
}
for(int i=0;i<=n;i++)
cout<<f[n][i]<<" ";
cout<<endl;
return 0;
}