期望DP从入门到出门

开完会后决定重写一遍课件

持续更新

数学定义:

期望的公式:

我们先来明确两个概念

随机变量:

我们扔一颗骰子,设 \(x\) 为抛出的点数,显而易见 \(x\) 的取值有六个,分别是 1,2,3,4,5,6。

但是每次扔出的点数是随机的、不可预计的,即 \(x\) 的取值有不确定性,像 \(x\) 这种变量就称作随机变量。

离散型随机变量:

随机变量分为两种:离散型和连续型。

离散型:该变量的取值个数是有限的,可以一个一个列出的,就比如投骰子和扔硬币。

连续型:该变量的取值个数无限,数不尽的,比如随机变量 \(x\) 的全部取值为全体实数,那么 \(x\) 就为连续型变量。

对于一组离散型随机变量,它的所有取值乘上取值所对应概率的总和,就是数学期望。

\[E(X)= ∑_{i=1}^{n}x_i×P(X=x_i) \]

期望的性质:

\(X,Y\) 为随机变量,\(a,b,c\) 为常数

不知道什么性质:\(E(c)=c\)

显而易见,根据期望的公式,\(E(c)=c×P(c=x)=c\)

可乘性:当 \(X\)\(Y\) 相互独立时,\(E(XY)=E(X)×E(Y)\)

证明:

\[\begin{aligned} E(XY)&=∑_{i=1} ∑_{j=1} x_iy_jP(X=x_i,Y=y_j)\\ &=∑_{i=1} x_iP(X=x_i)×∑_{j=1}y_jP(Y=y_j)\\ &=E(X)×E(Y) \end{aligned} \]

\(X\)\(Y\) 相互独立时,\(P(XY)=P(X)×P(Y)\)

可加性:\(E(X+Y)=E(X)+E(Y)\)

证明:

根据期望的定义来计算:

\[\begin{aligned} E(X+Y)&=∑_{i=1} ∑_{j=1} (x_i+y_j)P(X=x_i,Y=y_j)\\ &=∑_{i=1} ∑_{j=1} x_iP(X=x_i,Y=y_j)+∑_{i=1} ∑_{j=1} x_iP(X=x_i,Y=y_j)\\ &=∑_{i=1} x_iP(X=x_i)∑_{j=1} P(Y=y_j) + ∑_{j=1} y_jP(Y=y_j)∑_{i=1} P(X=x_i)(概率的总和必定是一)\\ &=E(X)+E(Y) \end{aligned} \]

根据前两条可以推出:\(E(aX)=aE(X)\)

再结合第三条可以进一步得出:\(E(aX+b)=aE(X)+b\)

以上为期望的基础知识,大家还是掌握好一点,毕竟后面的题推式子的时候你可能什么都不理解(

期望DP:

梯丹

当然不可能全讲,只能挑会讲的几道讲,剩下的就当成作业罢。

前面几题严格来说并不算 \(dp\),只能算简单递推,就作为引入罢。

o s u

这三题思路大体一致连名字都差不多,所以放在一块讲。

题意应该很好理解,解法也很好想,为了方便讲,我们先把第一个中的 0 1 ? 分别转化成成功率为 0 1 0.5?表示 01 的出现概率相等,也就是 0.5 的胜率)。

我们用 \(len1\) 来表示打到当前操作 combo 长度的期望,\(len2\) 表示长度平方的期望,\(f\) 为期望得分,\(p\) 为成功概率。

先看前两道题,它们的得分贡献为平方,比较好维护,如果当前成功,那么:

\[\begin{gather*} f\to f+2×len1+1\\ len1\to len1+1\\ \end{gather*} \]

因为 \(f\) 是长度的平方,\(len1+1\) 的话 \(sum\) 就要加上 \(2×len+1\)

如果失败了,那么 \(len1\) 要清零,\(sum\) 不变。

综合一下,可以推出式子:

\[\begin{gather*} f+=(2×len1+1)×p+0×(1-p)=p(2×len1+1)\\ len1=(len1+1)×p+0×(1-p)=p(len1+1)\\ \end{gather*} \]

然后再看最后一题。从平方变成了立方,不过也好维护:

\[\begin{gather*} f+=[3×(len1+len2)+1]×p\\ len2=(len2+len1×2+1)×p\\ len1=(len1+1)×p\\ \end{gather*} \]

SP1026 FAVDICE - Favorite Dice

\(f_i\) 为已经掷到过 \(i\) 个不同的面,使 \(n\) 个面全部被掷到的期望投掷次数。

显然的,有 \(\frac{i}{n}\) 的概率掷到已经掷到过的面,有 \(\frac{n-i}{n}\) 的概率掷到未被掷到的面。

可以推出式子:

\[f[i]=\frac{i}{n}×(f[i]+1)+\frac{n-i}{n}×(f[i+1]+1) \]

移个项:

\[\frac{n-i}{n}×f[i]=\frac{n-i}{n}×f[i+1]+1\\ f[i]=f[i+1]+\frac{n}{n-i} \]

最后直接递推就行

#include<bits/stdc++.h>
using namespace std;
double ans,n;
int T;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lf",&n);
		for(int i=1;i<=n;++i)  ans+=(n*1.0/(i));
		printf("%.2lf\n",ans);
		ans=0;
	}
	return 0;
}

P3802 小魔女帕琪

稍微思考一下可以得出,任意连续 \(7\) 位构成终极魔法的概率是一样的。那么问题就转换成了求长度为 \(7\) 的序列中触发的期望,最后再 \(×(n-6)\),就可以算出最终的期望。

先求形如 \(1,2,3,4,5,6,7\) 的触发概率

\(N=a_1+a_2+...+a_7\)

很好得出:

\[P=\frac{a_1}{N}×\frac{a_2}{N-1}×\frac{a_3}{N-2}×\frac{a_4}{N-3}×\frac{a_5}{N-4}×\frac{a_6}{N-5}×\frac{a_7}{N-6} \]

考虑到顺序没有影响,所以有 \(7!\) 种排列,所以:

\[P=7!×\frac{a_1}{N}×\frac{a_2}{N-1}×\frac{a_3}{N-2}×\frac{a_4}{N-3}×\frac{a_5}{N-4}×\frac{a_6}{N-5}×\frac{a_7}{N-6} \]

可以得出最终答案:

\[E=(N-6)P=7!×\frac{a_1}{N}×\frac{a_2}{N-1}×\frac{a_3}{N-2}×\frac{a_4}{N-3}×\frac{a_5}{N-4}×\frac{a_6}{N-5}×a_7 \]

#include<bits/stdc++.h>
using namespace std;
double a[8],n;
int main()
{
	for(int i=1;i<=7;++i)  scanf("%lf",&a[i]),n+=a[i];
	printf("%.3lf",5040.0*a[1]/n*a[2]/(n-1.0)*a[3]/(n-2.0)*a[4]/(n-3.0)*a[5]/(n-4.0)*a[6]/(n-5.0)*a[7]);
	return 0;
}

Ilya and Escalator

虽然有点恶意评分的意思,但也可以来水一道蓝题锻炼一下推式子的能力。

用二维的状态来解决这个问题,设 \(f_{i,j}\) 为时间为 \(j\) 秒时电梯里有 \(i\) 人的概率。

\(p\) 的概率人数和秒数均增加,有 \(1-p\) 的概率只有秒数增加,于是可以推出式子:

\[f_{i,j}=p×f_{i-1,j-1}+(1-p)×f_{i,j-1} \]

最后特判一下边界条件:当 \(j=n\) 时电梯人满了,所以一定可以从 \(f_{i,j-1}\) 转移而来:

\[f_{i,n}=f_{i,j-1}+p×f_{i-1,j-1} \]

因为要求的是期望人数,所以 \(ans=\sum_{i=1}^{n} f_{i,j}×i\)

#include<bits/stdc++.h>
using namespace std;
double n,t,p,dp[2010][2010],ans;
int main () 
{
    cin>>n>>p>>t;
    dp[0][0]=1;
    for(int i=1;i<=t;++i)
        for(int j=0;j<=n;++j)
        {
            if(j)  dp[i][j]+=dp[i-1][j-1]*p;
            if(j!=n)  dp[i][j]+=dp[i-1][j]*(1-p);
            else dp[i][j]+=dp[i-1][j];
        }
    for(int i=1;i<=n;++i)  ans+=dp[(int)t][i]*(i*1.0);
    printf("%.6lf",ans);
    return 0;
}

Bag of mice

典?

oi-wiki 用的就是这道题

先贴一份代码在这里,题解先咕着

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int w,b;
double f[N][N];
int main()
{
	io>>w>>b;
	for(int i=1;i<=w;++i)
		f[i][0]=1.0,f[i][1]=1.0*i/(i+1);
	if(!b||b==1)return printf("%.9lf\n",f[w][b]),0;
	for(int i=1;i<=w;++i)
		for(int j=2;j<=b;++j)
		{
			f[i][j]=1.0*i/(i+j);
			f[i][j]+=1.0*j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2)*f[i-1][j-2];
			if(j^2)f[i][j]+=1.0*j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2)*f[i][j-3];
		}
	printf("%.9lf\n",f[w][b]);
    return 0;
}

先考虑边界

P6835 [Cnoi2020] 线形生物

好题,可以加强对期望线性性质的理解。

\(f_i\) 为从当前节点 \(i\)\(i+1\) 所需的期望步数。

根据上面讲的可加性,可以得出 \(E_{x\to y} = \sum_{i=x}^{y-1} E_{i\to i+1}=\sum_{i=x}^{y-1} f_i\)

所以我们只需要单独算出每个点的 \(f\) 值,再求和即可。

现在问题就转化为了求 \(f_i\) ,设 \(cnt\)\(i\) 返祖边的数量。

假如在当前点选择了返祖边,那么需要再从 \(v\) 走回 \(u\),即 \(\sum_{i=v}^{u-1} f_i\),显而易见可以用前缀和维护,最后还要加上走了返祖边的贡献 \(1\),简化为:\(sum_i-sum_{j-1}+1\)

现在所有返祖边的贡献都可以求出了,同时还需要加上直接往后走的情况,所以方程为:

\[\begin{gather*} f_i=\frac{\sum_{i=1}^{cnt_i}(sum_i-sum_{v_i-1}+1)+1}{cnt_i+1}\\ 这个式子已经可以算出结果了,不过还可以继续简化:\\ (cnt_i+1)f_i=cnt_i*sum_i-\sum_{i=1}^{cnt_i}sum_{v_i-1}+1+cnt_i\\ (cnt_i+1)f_i=cnt_i*(sum_{i-1}+f_i)-\sum_{i=1}^{cnt_i}sum_{v_i-1}+1+cnt_i\\ f_i=cnt_i*sum_{i-1}-\sum_{i=1}^{cnt_i}sum_{v_i-1}+1+cnt_i\\ \end{gather*} \]

复杂度为 \(O(n+m)\),不简化会慢一点。

#include<bits/stdc++.h>
using namespace std;
long long mod=998244353;
long long qp(long long a,long long b,long long ans=1)
{
    for(;b;b>>=1,a=(a*a)%mod)ans=ans*(b&1?a:1)%mod;
    return ans;
}
int id,n,m,c,ans;
vector<int>s[1000100];
long long dp[1000100],sum[1000100];
int main () 
{
    cin>>id>>n>>m;
    for(int i=1;i<=m;++i)
    {
        int u,v;
        cin>>u>>v;
        s[u].push_back(v);
    }
    for(int i=1;i<=n;++i)
    {
        long long len=s[i].size(),cnt=0;
        for(auto j:s[i])  cnt+=sum[j-1];
        dp[i]=((len*sum[i-1]-cnt+len+1)%mod+mod)%mod;
        sum[i]=(sum[i-1]+dp[i])%mod;
    }
    cout<<sum[n];
    return 0;
}

P3750 [六省联考 2017] 分手是祝愿

好典

仔细想想不难发现:对于每个灯,只有它后面的灯才能影响到它,换句话说:对于一个点,在后面的点操作完后,如果这个灯还亮着,这个点就必须按。所以我们可以从后面扫一遍,把必须要按的灯记录下来,共 \(cnt\) 个,特别的,如果 \(cnt\le k\),直接输出 \(cnt\) 就行。并且对于每个不必要的点,如果按到了,就必须要按回去来消除影响。

\(f[i]\) 为由剩余 \(i\) 个必须要按的灯,变为 \(i-1\) 个必须要按的灯所需次数的期望。然后就是熟悉的分情况:

  • 按到必要的灯,因为必要的灯已经确定了,所以顺序没有影响,概率为 \(\frac{i}{n}\)

  • 按到不必要的灯(或者是后面已经灭了的灯),概率为 \(\frac{n-i}{n}\) ,此时必要灯的数量变为 \(i+1\) ,所以还需 \(f[i+1]+f[i]\) 次操作才能变为 \(i-1\) 个。

\[f[i]=\frac{n-i}{n}×(f[i+1]+f[i])+1 \]

简化一下:

\[f[i]=\frac{n+(n−i)×f[i+1]}{i} \]

最终求出答案(不要忘了 \(+k\) ):

\[sum=n!×(k+∑_{i=k+1}^{cnt} f[i]) \]

#include<bits/stdc++.h>
using namespace std;
long long mod=100003;
int n,k,cnt;
long long c=1,f[100001];
int a[100001];
long long qp(long long a,long long b,long long ans=1){for(;b;b>>=1,a=(a*a)%mod) if(b&1)  ans=(ans*a)%mod;return ans;}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)
    {
        c=(c*i)%mod;
        scanf("%d",&a[i]);
    }
    for(int i=n;i>=1;--i)
    {
        if(!a[i])  continue;
        for(int j=1;j*j<=i;++j) 
        {
            if(i%j==0)  a[i/j]^=1,a[j]^=1;
            if(j*j==i)  a[j]^=1;
        }
        cnt++;
    }
    if(cnt<=k)  return printf("%lld",c*cnt%mod),0;
    f[n]=1;
    for(int i=n-1;i;--i) f[i]=(long long)(n-i)*(f[i+1]+1)%mod*qp(i,mod-2)%mod+1;
    long long ans=k;
    for(int i=k+1;i<=cnt;++i)  ans=(ans+f[i])%mod;
    printf("%lld",ans*c%mod);
    return 0;
}
posted @ 2023-07-11 17:08  鳶一折紙  阅读(133)  评论(3编辑  收藏  举报