考虑到对于期望的不熟悉,写一篇blog来总结一下
期望是权与概率的乘积,可以理解为所有状态的平均权结果
期望的定义和性质指定了我们在做期望dp时的方向,下面我们考虑三种方法
方法1
考虑期望的定义
这相当于是一种相当直接的方法
考虑对于找到样本空间的大小来计数,或者考虑某个状态的概率
这必须建立在我们很容易了解到一个状态的权,并且我们很容易就能通过计数等一系列技巧得到我们的概率的这个大前提下
同时,这样指定了我们状态有限或者说状态数相当少这个条件
方法2
考虑全期望公式
这表明期望不会在状态转移中途消失
这里引出一个很有意义并且好用的观点:
状态的转移对应着图上的边,状态对应着点
虽然这个东西很显然,但是这就是我们全期望公式解题的要点
我们考虑逆序定义状态,这样的原因在于我们要保证到达终点(最终状态)的概率为1
在这种情况下我们才能满足我们的期望在中途不存在漏出的情况
(上面的说法并不完全,实际上你可以考虑删去不可能的那部分转移开做这类似的问题,但是这往往就会导致问题变得复杂,难以思考)
例题便是我们可爱的绿豆蛙
方法3
根据期望的线性性,他指出
这表明我们求总共的期望,可以考虑每一部分的贡献,这是一种几乎万能的做法
比如我们考虑绿豆蛙这道题,实际上我们不从状态转移来考虑,就可以看作是我们对于每一个边求出经过他的概率,再考虑他贡献的权就好了
实际可以发现,这种方法和方法2和方法1在某些题目中基本是等价的
但是我们发现,这么思考这个问题后,方法二中在求概率时求期望就显得没有必要
例题
我们简单考虑一道例题:CF518D
这道题我们很容易想到,我们对于每一个状态考虑概率,转化成一个只和概率有关的dp
这样我们相当于用方法1来考虑了这道题
但是我们考虑到这个贡献实际上可以看作是在边上的
那么考虑我们的第三种做法,我们直接就可以考虑用期望线性性算出期望
下面分别给出两种解法的代码,都不算长,这里不是说哪种更加优秀,更想强调的是,要针对不同的问题选择最好考虑的方面
code1
:
#include <cstdio>
using namespace std;
double dp[2010][2010];
int n,t;
double p;
int main()
{
scanf("%d%lf%d",&n,&p,&t);
dp[0][0]=1;
for(int i=0;i<t;i++)
{
dp[i+1][n]+=dp[i][n];
for(int j=0;j<n;j++)
{
dp[i+1][j+1]+=dp[i][j]*p;
dp[i+1][j]+=dp[i][j]*(1-p);
}
}
double ans=0;
for(int i=0;i<=n;i++) ans+=i*dp[t][i];
printf("%.6f
",ans);
}
code2
:
#include <cstdio>
using namespace std;
double dp[2010][2010];
int n,t;
double p,ans;
int main()
{
scanf("%d%lf%d",&n,&p,&t);
dp[0][0]=1;
for(int i=0;i<t;i++)
{
dp[i+1][n]+=dp[i][n];
for(int j=0;j<n;j++)
{
dp[i+1][j+1]+=dp[i][j]*p;
dp[i+1][j]+=dp[i][j]*(1-p);
ans+=dp[i][j]*p;
}
}
printf("%.6f
",ans);
}
总结
期望dp的范畴基本也就上面所说,只是每个人的考虑方式不同,实现方式也不同
有人认为期望dp就是一个计数一样的东西,但是我们可以发现,它相比计数具有更多美妙的性质,用来简化过程
(至于这个太短了,看后面多久写一个题集的博文吧)
(有三道有点裸的题目:分别是bzoj 3450,bzoj 4318,洛谷T142527)