(简记)概率 期望类 DP

概率/期望类 DP

\[\text{概率}=\frac{\text{合法方案数}}{\text{总方案数}},\text{期望}=\sum\text{事件权值}\times P(\text{事件}) \]

OI-wiki Intro

P1365 WJMZBMR打osu! / Easy

更好的题解

强调对于离散型随机变量的全期望公式的使用、期望的线性性。笔者水平很有限,只能大致给出很小一部分模糊的定义:

对于离散型随机变量(不考虑连续的实数域毕竟积分超出了学习范围)对于每个事件 \(X\) 对应概率 \(P\)\(p_i=P\{x_i=X\}\),整体期望 \(\sum x_i p_i\) (当且仅当其绝对收敛),记其值为 \(EX\)

性质1\(E(X+Y)=EX+EY\)
性质2\(E(XY)=EX\cdot EY\)\(XY\)\(X,Y\) 组合相乘后的事件)

我们的期望拆分依据全期望公式展开,本文不进行详细介绍。

  • \(L_i\) 表示做完前 \(i\) 次点击所获得的 combo。特别地,如果第 \(i\) 位是 ?,分别有 \(\frac{1}{2}\) 的概率取到 \(0\)\(L_{i-1}+1\)
  • \(F_i\) 表示做完前 \(i\) 次点击所获得的得分,如果取到 ?,转移就是各有 \(\frac{1}{2}\) 的概率取到 \(F_{i-1}\)\(F_{i-1}+L_i^2-L_{i-1}^2=F_{i-1}+(L_{i-1}+1)^2-L_{i-1}^2=F_{i-1}+2L_{i-1}+1\),特别地,这里对 \(L_{i-1}\) 做了钦定其 \(+1\) 的指示。

我们发现了,上面定义的两个变量都是随机变量,就是说,我们不能确保它有一个确定的取值,但是每一个取值都会有一个概率,所以我们不妨也给他们直接计算期望 \(E(F_i),E(L_i)\),由性质1,我们会发现都计算期望的情况下是有普适且正确的转移的,这来源于期望的线性性,也就是为什么我们能够直接用 \(L_{i-1}\) 的期望转移 \(F_i\) 的原因。

本题中存在随机变量 \(\Delta i=F_i-F_{i-1}\),取 o\(\Delta i=2L_{i-1}+1\),取 x\(\Delta i=0\),取 ? 时各有 \(\frac{1}{2}\) 的概率取到上面两个之一。

本题中需要解决的是取 ? 的问题,我们不妨设一个 \(\texttt{len}\) 记录当前 combo 的期望,然后用一个 \(E(F_i)\) 来转移 \(E(F_i)=E(F_{i-1}+\Delta i)=E(F_{i-1})+E(\Delta i)\),即可以写成一个前缀和的形式,考虑 \(E(\Delta i)\) 的单独计算即可。根据定义,\(E(\Delta i)=\sum x_j p_j=\frac{1}{2}\times 0+\frac{1}{2}\times(2E(L_{i-1})+1)=E(L_{i-1})+\frac{1}{2}\),这样线性递推就解决了本题。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n;
char s[N];
long double f[N],L;
int main(){
	scanf("%d",&n);
	scanf("%s",s+1);
	f[0]=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='o')f[i]=f[i-1]+2*L+1,L++;
		else if(s[i]=='x')f[i]=f[i-1],L=0;
		else f[i]=f[i-1]+L+0.5,L=(L+1)/2;
	}
	printf("%.4Lf",f[n]);
	return 0;
}

P3232 [HNOI2013] 游走

本题的期望有点特殊,因为在无向有环连通图中转移不是线性的,可能是环形的。具体来说,我们先对每个节点(除 \(n\))考虑期望经过次数 \(E(x)\),然后就可以通过每条边的 \(E(u,v)=\frac{E(u)}{deg_u}(u\neq n)+\frac{E(v)}{deg_v}(v\neq n)\)(分别表示从 \(u\)\(v\)\(v\)\(u\)),考虑的出发角度是一个节点会任选 \(deg_i\) 条与之相连的边中(包括重边、自环)等概率任意一条,然后按期望从大到小的顺序从小到大赋边编号即可。

这样我们同样可以推出点期望次数转移 \(E(x)=\begin{cases}\sum_{(x,i)\in E}\frac{E(i)}{deg_i} +1 & x=1\\\sum_{(x,i)\in E}\frac{E(i)}{deg_i}& \text{otherwise}\end{cases}\),然后可以用高斯消元求解每个 \(E(x)\),这题就做完了。

#include<bits/stdc++.h>
using namespace std;
const int N=505;
long double f[N][N],ans;
int n,m,deg[N];
struct Edge{int u,v;long double w;}e[125005];
vector<int>G[N];
bool cmp(Edge x,Edge y){return x.w>y.w;}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&e[i].u,&e[i].v),
		deg[e[i].u]++,deg[e[i].v]++;
		G[e[i].u].push_back(e[i].v);
		G[e[i].v].push_back(e[i].u);
	}
	for(int i=1;i<n;i++){
		if(i==1)f[i][n]=-1;
		for(int j:G[i])
			if(j!=n)f[i][j]+=1.0/deg[j];
		f[i][i]--;
	}
	for(int i=1;i<=n-1;i++){
		int pos=0;
		for(int j=i;j<=n-1;j++)
			if(f[j][i]){pos=j;break;}
		swap(f[i],f[pos]);
		long double num=f[i][i];
		for(int j=1;j<=n;j++)
			f[i][j]/=num;
		for(int j=1;j<=n-1;j++){
			if(j==i)continue;
			if(f[j][i]){
				num=f[j][i]/f[i][i];
				for(int k=1;k<=n;k++)
					f[j][k]-=num*f[i][k];
			}
		}
	}
	for(int i=1;i<=m;i++)
		e[i].w=f[e[i].u][n]/deg[e[i].u]+f[e[i].v][n]/deg[e[i].v];
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=m;i++)
		ans+=e[i].w*i;
	printf("%.3Lf",ans);
	return 0;
}

P3830 [SHOI2012] 随机树

整数概率公式的经典应用,即 \(E(x)=\sum_{i=1}^{\infty}P(x\geq i)\),具体证明展开式子 \(\sum_{i=1}^{\infty}iP(X=i)\) 然后交换一下循环位置即可。

这里直接搬运了题解,注意本题根节点深度为0。显然 \(n\) 个叶子节点的树是由根节点经过 \(n-1\) 次展开操作得到。

\(f(k)\) 表示有 \(k\) 个叶子节点的树的叶子节点平均深度,在一个有 \(k-1\) 个叶子节点的树中随机选择一个叶子节点展开,则树的叶子节点深度总和会增加 \(f(k-1)+2\)。因此有状态转移:

\[f(k)=\frac{f(k-1)\times(k-1)+f(k-1)+2}{k}=f(k-1)+\frac{2}{k} \]

对于树深度期望,套用整数概率公式,设 \(g(k,j)\) 表示有 \(k\) 个叶子,树深度 \(X\geq j\) 的概率。

转移时枚举左右子树有多少个叶子:

\[g(k,j)=\sum_{i=1}^{k-1}\frac{1}{k-1}(g(i,j-1)+g(k-i,j-1)-g(i,j-1)\times g(k-i,j-1)) \]

式子含义:左右只要一边深度 \(\geq j-1\) 即可,因此需要减掉两边都 \(\geq j-1\) 的概率。

最后答案即为

\[E(X)=\sum_{i=1}^{n-1} P(X\geq i)=\sum_{i=1}^{n-1} g(n,i) \]

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n;
long double f[N],g[N][N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int q;scanf("%d%d",&q,&n);
	if(q==1){
		for(int i=2;i<=n;i++)
			f[i]=f[i-1]+2.0/i;
		printf("%.6Lf",f[n]);
	}
	else {
		g[1][0]=1;
		for(int k=2;k<=n;k++){
			g[k][0]=1;
			for(int j=1;j<k;j++)
				for(int i=1;i<k;i++)
					g[k][j]+=(1.0/(k-1))*(g[i][j-1]+g[k-i][j-1]-g[i][j-1]*g[k-i][j-1]);
		}
		long double ans=0;
		for(int i=1;i<=n-1;i++)ans+=g[n][i];
		printf("%.6Lf",ans);
	}
	return 0;
}
posted @ 2025-08-11 10:44  TBSF_0207  阅读(25)  评论(0)    收藏  举报