概率dp的内容
概率dp 研究有关于概率,步数,期望的等问题。
- 数学期望 P=Σ每一种状态*对应的概率。
- 因为不可能枚举完所有的状态,有时也不可能枚举完,比如抛硬币,有可能一直是正面,etc。 但是现在发现大多数题就是手动找公式或者DP推出即可,只要处理好边界,然后写好方程,代码超级简短。与常规的求解不同,数学期望经常逆向推出。
- 比如常规的dp[x]可能表示到了x这一状态有多少,最后答案是dp[n]。而数学期望的dp[x]一般表示到了x这一状态还差多少,最后答案是dp[0]。
- 什么时候可以互相计算呢?关键在于所求期望的变量值是否会随着过程变化而变化!!!而不仅仅和所处位置有关!!!这种情况下,我们可以记录到当前状态所需的步数,最后就可以算期望。
例题:
1.甩一个n面的骰子,问每一面都被甩到的次数期望是多少。
顺推:第一次选一张即可达到目的,然后剩下n-1张,从n张中选择n-1张有用的中的一张,概率是(n-1)/n,故平均每一次期望是n/(n-1),后推求和即可。(平均期望该情况下好算,因此可以顺推)。
逆推:已知dp[n]表示所在第n个需要到第n个的期望步数,则dp[n]=0;已知dp[i+1],求dp[i]的解释:,表示:当前第i个到第n个的期望等于,当前期望有i/n的概率在自身,有(n-i)/i的概率到dp[i+1].
2.有n个妖怪,每个妖怪有一个固定的战斗力c,师傅也有一个初始战斗力f0。每天,师傅会随机选择一个妖怪决斗,如果打得赢ft>c,就可以逃出去,逃出去要t[]天,毕竟超人不会飞;
否则,师傅会不甘心,当天他会拿出秘籍练功,将自己变强,f(t+1)=f(t)+c[],第二天寻找下一次机会。
问师傅能够逃脱可怕的妖怪,继续追求去印度吃手抓饼的梦想的天数的数学期望day。
顺推:概率dp,将当前的战斗力看成dp[x],每次变成下一个战斗力的转移为dp[i+c]=dp[i+c]+dp[i]*(i->i+c的概率),则出去的天数的期望为转移到状态*天数,由于有无线个,故无法计算。(概率好求,期望难算)
逆推:用dp[i]表示战斗力为i时出去的期望。最后的结果是成功逃离,因此每种怪都能顺利逃离,故当前打这种怪能逃离的期望天数是,所以如果i>c[j] dp[i]+=(int)(p*c[j]*c[j])/n;,如果i<=c[j] dp[i]+=(dfs(i+c[j])+1)/n;反向打表也即是采用dfs的方式处理,代码如下:
int n,f;
const double p=(1.0+sqrt(5.0))/2.0;
double dp[maxn];
int c[maxm],t[maxm];
double dfs(int x)
{
if(dp[x]>0)return dp[x];
for(int i=0;i<n;i++)
{
if(x>c[i])dp[x]=dp[x]+(double)t[i]/n;
else dp[x]=dp[x]+(dfs(x+c[i])+1.0)/n;
}
return dp[x];
}
int main()
{
while(cin>>n>>f)
{
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
{
scanf("%d",&c[i]);
t[i]=p*c[i]*c[i];
}
double ans=dfs(f);
printf("%.3f\n",ans);
}
return 0;
}
3.循环型期望dp
样例与代码:
1.树形(DAG):
其中p1+p2+p3=1;
struct edge
{
int to;
double p;
double a;//转移花费,步数等于1
};
vector<edge> vec[maxn];
double e[maxn];
double getE(int rt,int fa)
{
if(e[rt]>0)return e[rt];
int s=vec[rt].size();
for(int i=0;i<s;i++)
{
edge v=vec[rt][i];
if(v.to==fa)continue;
e[rt]=e[rt]+(getE(v.to,rt)+v.a)*v.p;
}
return e[rt];
}