概率期望相关
收到【】命令,开整。
一些基础定义
建议前往OI-wiki查看。
概率
不会吧不会吧不会真有人不知道概率吧,请自行翻阅初中/高中数学教科书。\(P(A)\) 表示事件 \(A\) 发生的概率。显然,\(P(A) \in [0,1]\)。
随机变量。
字面意思。一个随机变量,是一个取值由随机事件决定的变量。下文中,\(P(X=a)\) 表示随机变量 \(X=a\) 的几率。
独立性
还是字面意思。直观地说,我们认为两个东西独立,当它们在某种意义上互不影响。
期望
已知一个随机变量 \(X\),有 \(X\) 的期望 \(\displaystyle E(X)=\sum i\times P(X=i)\)。另外一个理解是所有可能的值的加权平均数。
期望的一些性质:
- 全期望公式:\(\displaystyle E(Y)=\sum_{\alpha \in I(x)}P(X=\alpha)\times E(Y|(x=\alpha))\),其中 \(X,Y\) 是随机变量,\(E(Y|A)\) 是在 \(A\) 成立的条件下 \(Y\) 的期望(即所谓“条件期望”)。
貌似没啥用 - 期望的线性性:对于任意两个随机变量 \(X,Y\) (不要求相互独立),有 \(E(X+Y)=E(X)+E(Y)\)。利用这个性质,可以将一个变量拆分成若干个互相独立的变量,分别求这些变量的期望值,最后相加得到所求变量的值。
- 乘积的期望:当两个随机变量 \(X,Y\) 相互独立时,有 \(E(X\times Y)=E(X)\times E(Y)\)。
好,以上就是你所需要的前置知识了,剩下任务就是解题了。
期望的常用套路:
- 根据期望的线性性,把期望拆成若干的变量的期望,然后逐个解决。
- 概率/期望 dp,状态设计成概率或是期望依照题目而定。
- 把期望转换成所有情况的和除以情况数,多用于取模的情况。
不给我取模的期望题都是屑!11。
以上就是常见的套路了,具体怎么操作就要看题目了。
为什么明明概率 dp 是 zmf 写的,但是给我的题目很多都是概率 dp。
BZOJ1426P4550 收集邮票 怎么 2021 年了还有 BZOJ 这个东西啊。
这个题我认为是比较经典的,比较考虑对期望的理解,也算是期望 dp 的一种常用套路。
先考虑一个弱化版的问题:如果每次购买只需要一块钱,那么最终的答案是多少?
这个我会,这是经典结论,答案是 \(\displaystyle \sum_{i=1}^n \frac{n}{i}\)。
设 \(f_i\) 表示当前有 \(i\) 张邮票,到 \(n\) 张期望需要的钱数,那么有 \(f_n=0\)。
当 \(i \ne n\) 的时候,有 \(\frac i n\) 的几率抽到一样的,有 \(\frac {n-i}{n}\) 的几率抽到新的,那么有 \(\displaystyle f_i = \frac{i}{n} f_i + \frac{n-i}{n} f_{i+1}+1\)。
可是,这个转移里怎么两边都有 \(f_i\) 啊,怎么转移啊。
移项即可!可以得到 \(\displaystyle f_i=f_{i+1}+\frac{1}{n-i}\)。答案就是 \(f_0\),经过数学推导之后发现就是 \(\displaystyle \sum_{i=1}^n \frac{n}{i}\),然而这一点用都没有。
再回来这个题目:有个很 naive 的想法就是:如果当期需要的次数是 \(x\) 就相当于要 \(\frac {x\times (x+1)}{2}\) 元,那么答案不就是 \(\frac{f_0 \times (f_0+1)}{2}\) 吗,这个题太简单了!
然后你发现你过不了样例。然后你发现你假了。这是一个很普遍的假的原因:平方的期望不等于期望的平方。(手动模拟几个就行了。)
然后怎么做呢……设 \(g_i\) 表示当前有 \(i\) 张邮票,到 \(n\) 张期望需要的钱数。显然 \(g_n=0\)。
有 \(\displaystyle \frac{i}{n}\) 的几率抽到重复的,\(\displaystyle \frac{n-i}{n}\) 的几率抽到不重复的。那么有 \(\displaystyle g_i = \frac{i}{n} \times (g_i+f_i+1)+ \frac{n-i}{n} \times (g_{i+1}+f_{i+1}+1)\)。
这个东西是怎么来的啊?
解释一下:\(g_i+f_i+1\) 的意义是在当前情况下,所有之后所取得的费用 \(+1\)(总共期望就是 \(+f_i\))以及当前取到的费用 \(+1\)(为啥是 \(+1\)?因为其他要加的前面都加过了(我在这里想了很久))。\(g_{i+1}+f_{i+1}+1\) 同理。
然后整理一下式子得到:\(\displaystyle g_i=\frac{i}{n-i}\times f_i+g_{i+1}+f_{i+1}+\frac{n}{n-i}\)
代码就很好写了。
点击查看代码
```cpp #includeinline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f=!(c45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
const int M=1e4+10;
int n;
double f[M],g[M];
signed main()
{
n=read();
for (int i=n-1;i>=0;i--)
f[i]=f[i+1]+1.0n/(n-i),
g[i]=g[i+1]+f[i+1]+i1.0/(n-i)f[i]+n1.0/(n-i);
printf("%.2lf",g[0]);
return 0;
}
</details>
</details>
[P5011 水の造题](https://www.luogu.com.cn/problem/P5011)
<details>
<summary>
</summary>
经典的拆贡献的题目,相信稍微熟悉一点期望就能够做出来。
把答案拆成“普通攻击”的期望和和“连击”的期望和。显然,这些期望和直接也是两两无关的,可以直接加起来。
先考虑“普通攻击”的期望,显然是 $\displaystyle \frac {\sum_{i=1}^k a_i}{k}$,那么 $n$ 次就是 $\displaystyle \frac {\sum_{i=1}^k a_i}{k} \times n$。
然后考虑“连击”的期望,显然一对相邻的数是 $\displaystyle \frac {2 \times \sum_{i=1}^k a_i}{k^2}$,那么 $n$ 次有 $n-1$ 对那么期望就是 $\displaystyle \frac {2 \times \sum_{i=1}^k a_i}{k^2}\times (n-1)$。加起来就好了。
注意 $n$ 有点大,一边读入一边取模。
<details>
<summary>
点击查看代码。
</summary>
```cpp
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
inline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f^=!(c^45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
const int M=1e6+10;
const int Mod=19491001;
string n;
int k,a[M],ans;
int poww(int a,int b)
{
int res=1;
while(b)
{
if (b&1)res=res*a%Mod;
a=a*a%Mod,b>>=1;
}
return res;
}
signed main()
{
cin>>n;int num=0;
for (int i=0;i<n.size();i++)
num=(num*10+n[i]-'0')%Mod;
cin>>k;int sum=0;
for (int i=1;i<=k;i++)sum+=read(),sum%=Mod;
int ans=sum*num%Mod*poww(k,Mod-2)%Mod+(num+Mod-1)%Mod*sum*2%Mod*poww(k*k%Mod,Mod-2);
ans%=Mod;cout<<ans<<endl;
return 0;
}
CF28C Bath Queue 这个题太简单了,我一眼就秒了。
把期望转换成所有情况的和除以情况数,虽然他没用给我模数,但是方案数显然是 \(m^n\),double
存的下。
因为 \(n \le 50\),这个数据范围大概是高维 dp。
最大值怎么处理?考虑刷表法。设 \(dp_{i,j,k}\) 表示前 \(i\) 个房间,用了 \(j\) 个人,最大值为 \(k\) 的方案数,那么枚举第 \(i+1\) 个房间的人 \(x (x \in [0,n-j])\),那么显然第 \(i+1\) 个房间的长度为 \(p=\left\lceil\dfrac{x}{a_{i+1}}\right\rceil\),不难得到 \(\displaystyle dp_{i+1,j+x,\max(k,p)}+=dp_{i,j,k}\)。
然后递推就可以了,答案就是 \(\displaystyle \frac {\sum_{i=0}^n dp_{m,n,i} \times i}{m^n}\)。
点击查看代码。
#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define mp make_pair
#define WT int T=read();while(T--)
#define NO puts("NO");
#define YES puts("YES");
using namespace std;
inline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f^=!(c^45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
const int M=55;
double dp[M][M][M],C[M][M];
int n,m,a[M];
signed main()
{
n=read(),m=read();
for (int i=1;i<=m;i++)
a[i]=read();
C[0][0]=1;
for (int i=1;i<=n;i++)
{
C[i][0]=1;
for (int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];//,cout<<C[i][j]<<' ';puts("");
}
dp[0][0][0]=1;
for (int i=0;i<m;i++)
for (int j=0;j<=n;j++)
for (int k=0;k<=n;k++)
for (int x=0;j+x<=n;x++)
dp[i+1][j+x][max(k,x/a[i+1]+(x%a[i+1]==0?0:1))]+=dp[i][j][k]*1.0*C[n-j][x];
double sum=pow(m,n),ans=0;
for (int i=0;i<=n;i++)
ans+=dp[m][n][i]*i;
ans/=sum;printf("%.20lf",ans);
return 0;
}
CF641D Little Artem and Random Variable 竟然是个构造题(
前言:这个题超级没有素质,网上找到的题解都有一定的问题,或者根本没有说清楚。
题意:你有两个骰子,第一个骰子的数为 \(x\) 的概率是 \(a_x\),第二个骰子的数为 \(x\) 的概率为 \(b_x\),骰子的数字从 \(1\) 到 \(n\),设第一个骰子的数为 \(a\),第二个骰子的数为 \(b\)。\(p1_i\) 表示 \(\max(a,b)=i\) 的几率,\(p2_i\) 表示 \(\min(a,b)=i\) 的几率。告诉你 \(p1,p2\),求出序列 \(a,b\)。
这是一个构造题,考虑如果已知 \(a\) 和 \(b\),那么显然有:
\(\displaystyle p1_i=(\sum_{j=1}^ia_i) \times (\sum_{j=1}^i b_i)-(\sum_{j=1}^{i-1}a_i) \times (\sum_{j=1}^{i-1} b_i)\)
\(\displaystyle p2_i = (\sum_{j=i}^n a_i) \times (\sum_{j=i}^n b_i)-(\sum_{j=i+1}^{n}a_i) \times (\sum_{j=i+1}^{n} b_i) = (1-\sum_{j=1}^{i-1}a_i) \times (1-\sum_{j=1}^{i-1} b_i)-(1-\sum_{j=1}^{i}a_i) \times (1-\sum_{j=1}^{i} b_i)\)
注意到都是 \(\sum\) 的形式,这个东西很有优化空间。设 \(\displaystyle S1_i = \sum_{j=1}^i a_j,S2_i=\sum_{j=1}^i b_j\),那么显然算出 \(S1,S2\) 就能得到 \(a,b\) 了。把 \(S1,S2\) 带入上式得:
\(p1_i=S1_i \times S2_i - S1_{i-1} \times S2_{i-1},p2_i=(1-S1_{i-1})\times (1-S2_{i-1}) - (1-S1_i)\times (1-S2_i)\)
发现这个东西长得很像差分的形式,很有优化空间:设 \(\displaystyle sp1_i =\sum_{j=1}^i p1_j,sp2_i = \sum_{j=1}^i p2_j\),带入上式得:
\(sp1_i = S1_i \times S2_i,sp2_i = (1-S1_{i-1})\times (1-S2_{i-1})\)。
那么有:
\(sp2_{i+1}=(1-S1_i)\times (1-S2_i)=1-(S1_i+S2_i)+S1_i\times S2_i\)
\(S1_i+S2_i = sp1_i-sp2_{i+1}+1\)。
结合以上内容可以得到:\(\begin{cases} S1_i+S2_i = sp1_i-sp2_{i+1}+1 \\ S1_i \times S2_i = sp1_i \end{cases}\)
解个方程就完事了。注意到有两个根,每次把小的给一边大的给一边就行。因为可能有略微的精度误差,求根的时候开根号的东西要和 \(0\) 取 max
。
点击查看代码。
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
inline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f^=!(c^45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
const int M=1e5+10;
int n;
double maxn[M],minn[M],sum1[M],sum2[M],p1[M],p2[M];
signed main()
{
cin>>n;
for (int i=1;i<=n;i++)cin>>maxn[i];
for (int i=1;i<=n;i++)cin>>minn[i];
for (int i=1;i<=n;i++)sum1[i]=maxn[i]+sum1[i-1];
for (int i=n;i>=1;i--)sum2[i]=minn[i]+sum2[i+1];
for (int i=1;i<=n;i++)
{
double x1=sum1[i],x2=sum1[i]-sum2[i+1]+1;
double d=sqrt(max(x2*x2-4*x1,0.0));
p1[i]=(x2-d)/2.0,p2[i]=(x2+d)/2.0;
}
for (int i=1;i<=n;i++)
printf("%.8lf ",p1[i]-p1[i-1]);puts("");
for (int i=1;i<=n;i++)
printf("%.8lf ",p2[i]-p2[i-1]);puts("");
return 0;
}
CF398B Painting The Wall 非常套路的期望 dp。
考虑如何设状态:\(dp_{i,j}\) 表示还有 \(i\) 行 \(j\) 列没有填的方案数,那么有:
\(\displaystyle dp_{i,j}=1+\frac{i \times (n-j)}{n^2} dp_{i-1,j}+ \frac{j\times (n-i)}{n^2}dp_{i,j-1}+\frac{i \times j}{n^2}dp_{i-1,j-1}+\frac{(n-i)\times (n-j)}{n^2}dp_{i,j}\)
移项之后就可以得到递推式,请自行推导。
边界情况就是 \(i=0\) 或者 \(j=0\) 的情况,这个也很容易去推,而且有个经典结论就是 \(\displaystyle dp_{0,i}=dp_{i,0}=n\times \sum_{i=1}^i \frac 1 i\)。(这个经典结论在前文有提及)。
然而题目告诉我已经有几个点涂过色了,那么算出还剩多少没有涂色就可以了
点击查看代码。
#include<bits/stdc++.h>
using namespace std;
const int M=2e3+10;
double dp[M][M],H[M];
int n,m,n1,n2,r[M],c[M];
inline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f^=!(c^45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
int main()
{
n1=n2=n=read(),m=read();
for (int i=1;i<=m;i++)
{
int a=read(),b=read();
if (r[a]==0)r[a]++,n1--;
if (c[b]==0)c[b]++,n2--;
}
for (int i=1;i<=n;i++)H[i]=H[i-1]+1.0/i;
for (int i=1;i<=n;i++)
dp[0][i]=dp[i][0]=n*H[i];
for (int i=1;i<=n1;i++)
for (int j=1;j<=n2;j++)
dp[i][j]=(n*n+i*j*dp[i-1][j-1]+i*(n-j)*dp[i-1][j]+(n-i)*j*dp[i][j-1])*1.0/((i+j)*n-i*j);
printf("%.10lf",dp[n1][n2]);
return 0;
}
CF601C Kleofáš and the n-thlon 咋又是期望 dp,这玩意不是 zmf 写吗。
注意到得分最大为 \(10^5\),可以直接把得分设在状态里。
具体的,设 \(dp_{i,j}\) 表示前 \(i\) 场比赛,得分为 \(j\) 的期望,那么枚举第 \(i\) 场的得分 \(x\),注意 \(x \ne a_i\),可以得到 \(\displaystyle dp_{i,j}=\frac{i-1,j-x}{m-1} (x \ne a_i,x \in [1,m])\)。蛮好理解的。
显然可以用一个前缀和去优化这个式子,然后就做完了。时空复杂度均为 \(\mathcal{O}(n^2\times m)\),其中空间可以用滚动数组优化(但没必要)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
inline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f^=!(c^45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
const int M=110;
const int N=1010;
int a[M],n,m;
double dp[M][N*M],sum[M][N*M],ans;
signed main()
{
n=read(),m=read();int Sum=0;
for (int i=1;i<=n;i++)a[i]=read(),Sum+=a[i];
for (int i=1;i<=m;i++)
if (i!=a[1])dp[1][i]=1;
for (int i=1;i<=n*m;i++)
sum[1][i]=sum[1][i-1]+dp[1][i];
for (int i=2;i<=n;i++)
for (int j=1;j<=n*m;j++)
{
dp[i][j]=sum[i-1][j-1]-(j<=m?0:sum[i-1][j-m-1]);
if (j-a[i]>=0)dp[i][j]-=dp[i-1][j-a[i]];
dp[i][j]/=m-1;
// for (int k=1;k<=m&&k<=j;k++)
// if (k!=a[i])
// dp[i][j]+=dp[i-1][j-k]*1.0/(m-1);
sum[i][j]=sum[i][j-1]+dp[i][j];
}
for (int i=n;i<Sum;i++)ans+=dp[n][i];ans++;
printf("%.12lf",ans);
return 0;
}
这个题要取模,可以放心大胆的把问题转化为总情况数除以总方案数。
总方案数显然是 \(\displaystyle (\frac {n \times (n-1)}{2})^k\)。
对于总情况数,考虑 dp。设有 \(m\) 个数是 \(0\)。设 \(dp_{i,j}\) 表示交换 \(i\) 次之后,前 \(m\) 个数中有 \(j\) 个 \(0\) 的情况,那么最后的答案就是 \(dp_{k,m}\)。
转移的时候,计算让 \(0\) 的个数多一/少一/不变的方案数就可以了。一看时间复杂度 \(\mathcal{O}(n \times k)\),过不去。
发现这个转移的时候,每个数前面的系数都是不变的,可以矩阵优化,这样时间复杂度就可以做到 \(\mathcal{O}(n^3 \log k)\),随便过。
这个系数的话还是自己手推一下比较好,实在推不出来/调不出来再去看代码吧。相信都看到这里了推一个系数的能力大家都还是有的(
点击查看代码。
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
inline int read()
{
char c=getchar();int x=0;bool f=0;
for(;!isdigit(c);c=getchar())f^=!(c^45);
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
if(f)x=-x;return x;
}
const int Mod=1e9+7;
const int M=110;
int dp[M][M],n,k,t,a[M];
struct node
{
int a[M][M];
}ans,aa,res;
node mul(node a,node b)
{
node c;memset(c.a,0,sizeof(c.a));
for (int i=0;i<=t;i++)
for (int j=0;j<=t;j++)
for (int k=0;k<=t;k++)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%Mod;
return c;
}
int poww(int a,int b)
{
int res=1;
while(b)
{
if (b&1)res=res*a%Mod;
a=a*a%Mod,b>>=1;
}return res;
}
int inv(int a)
{
return poww(a,Mod-2);
}
node poww2(node a,int b)
{
while(b)
{
if (b&1)res=mul(res,a);
a=mul(a,a),b>>=1;
}return res;
}
signed main()
{
n=read(),k=read();
for (int i=1;i<=n;i++)
a[i]=read(),t+=a[i]^1;
int cnt=0;
for (int i=1;i<=t;i++)
cnt+=a[i]^1;
ans.a[0][cnt]=1;
for (int i=0;i<=t;i++)res.a[i][i]=1;
for (int i=0;i<=t;i++)
aa.a[i-1][i]=(t-i+1)*(t-i+1)%Mod,
aa.a[i][i]=(t*(t-1)/2+(n-t)*(n-t-1)/2+i*(t-i)+(t-i)*(n-2*t+i))%Mod,
aa.a[i+1][i]=(i+1)*(n-2*t+(i+1))%Mod;
aa=poww2(aa,k);ans=mul(ans,aa);
cout<<ans.a[0][t]*inv(poww(n*(n-1)/2,k))%Mod;
return 0;
}