概率期望
xukuan
2020-08-10 20:58:00
## 公式
期望=$\sum$ 概率\*收益
设E(A)表示A的期望
$E(AB)=E(A)*E(B)$
## 例题
### P4550 收集邮票
线性期望例题
用$g_i$表示取i张邮票的期望步数
这里收益都是1,所以期望=概率
显然$g_i=g_{i-1}+\frac{n}{i}$
用$f_i$表示取第i张邮票要花的钱
$f_i=\frac{n-i}{i}*(g_i+1)+g_{i-1}+f_{i-1}+1$
这个主要是收益的式子,过程比较乱,就不写了,根据这个去推:对于第i张邮票,为了获得它的平均价格是购买前i张邮票的期望次数之和
代码:
```cpp
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=10010;
ll n;
double g[N],f[N];
int main(){
cin>>n;
for(ll i=1; i<=n; i++){
g[i]=g[i-1]+n*1.0/i;
f[i]=f[i-1]+g[i-1]+(n-i)*1.0/i*(g[i]+1)+1;
}
printf("%.2lf",f[n]);
return 0;
}
```
### P1365 WJMZBMR打osu! / Easy
二维期望例题
用$len_i$表示第i个点连续期望有$len_i$个o,$f_i$表示第i个点连续得分为$f_i$
当$s_i='o'$时:
- $len_i=len_{i-1}+1$
- $f_i=f_{i-1}+len_i^2-len_{i-1}^2=f_{i-1}+2*len{i-1}+1$
当$s_i='x'$时
- $len_{i}=0$
- $f_i=f_{i-1}$
当$s_i='?'$时
当前点有$\frac{1}{2}$的概率为x,有$\frac{1}{2}$的概率为o:$f_i$,$len_i$的期望为上述的平均值。
- $len_i=\frac{len_{i-1}+1}{2}$
- $f_i$的期望为$\frac{f_{i-1}+f_{i-1}+2*len{i-1}+1}{2}=f_{i-1}+len_{i-1}+\frac{1}{2}$
代码:
```cpp
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=300010;
ll n;
char s[N];
double len,f[N];
int main(){
scanf("%lld",&n);
scanf("%s",s+1);
for(ll i=1; i<=n; i++){
if(s[i]=='o'){
f[i]=f[i-1]+2*len+1;
len++;
}
else if(s[i]=='x'){
f[i]=f[i-1];
len=0;
}
else if(s[i]=='?'){
f[i]=f[i-1]+len+0.5;
len=(len+1)/2;
}
}
printf("%.4lf",f[n]);
return 0;
}
```
### P1654 OSU!
高维期望例题
直接给式子
$(x+1)^3-x^3=3x^2+3x+1$
$f_i$表示答案的期望,$len2_i$表示连续长度的二次方的期望,$len1_i$连续长度的期望
$f_i=f_{i-1}+a_i*(3*len2_i+3*len1_i+1)\\len2_i=(len2_i+2*len1_{i}+1)*a_i\\len1_i=(len1_i+1)*a_i\\$
代码:
```cpp
//(x+1)^3-x^3=3x^2+3x+1
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=100010;
ll n;
double a[N],f[N],len1,len2;
int main(){
scanf("%lld",&n);
for(ll i=1; i<=n; i++) scanf("%lf",&a[i]);
for(ll i=1; i<=n; i++){
f[i]=f[i-1]+a[i]*(3*len2+3*len1+1);
len2=(len2+2*len1+1)*a[i];
len1=(len1+1)*a[i];
}
printf("%.1lf",f[n]);
return 0;
}
```
### P1850 换教室
多状态期望例题
用$f_{i,j,k}$表示前i节课申请j个,第i节课在哪个教室上课的期望,$k=1$表示换教室,$k=0$不换
$1 \leq v \leq 300$,可直接floyd求任意两点间距离
前一个到后一个的期望$=\sum$前一个的期望\*前一个向后一个转移的概率
由前一个推到后一个的时候
$f_{i,j,0}=\min(f_{i-1,j,0}+d_{c1,c3},f_{i-1,j,1})+f_{c1,c3}*(1-k_{i-1})+d_{c2,c3}*k_{i-1}$
$f_{i,j,1}=\min(f_{i-1,j-1,0}+d_{c1,c3}*(1-k_i)+d_{c1,c4}*k_i,f_{i-1,j-1,1}+d_{c2,c4}*k_i*k_{i-1}+d_{c2,c3}*k_{i-1}*(1-k_i)+d_{c1,c4}*(1-k_{i-1})*k_i+d_{c1,c3}*(1-k_{i-1})*(1-k_i)$
```cpp
#include<bits/stdc++.h>
#define ll long long
#define INF 2147483647
using namespace std;
double k[20010],dp[2010][2010][2],Min=INF;
ll a[2010][2010],c[20010],d[20010],n,m,v,e,f[2010][2010];
inline void floyd(){
for(ll k=1; k<=v; k++){
for(ll i=1; i<=v; i++){
for(ll j=1; j<=v; j++){
f[i][j]=f[j][i]=min(f[i][j],f[i][k]+f[k][j]);
}
}
}
}
int main(){
scanf("%lld %lld %lld %lld",&n,&m,&v,&e);
for(ll i=1; i<=n; i++) scanf("%lld",&c[i]);
for(ll i=1; i<=n; i++) scanf("%lld",&d[i]);
for(ll i=1; i<=n; i++) scanf("%lf",&k[i]);
for(ll i=1; i<=v; i++){
for(ll j=1; j<i; j++){
f[i][j]=f[j][i]=INF;
}
}
for(ll i=1; i<=n; i++){
for(ll j=0; j<=m; j++){
dp[i][j][0]=dp[i][j][1]=INF;
}
}
while(e--){
ll a,b,w;
scanf("%lld %lld %lld",&a,&b,&w);
f[a][b]=f[b][a]=min(f[a][b],w);
}
floyd();
dp[1][0][0]=dp[1][1][1]=0;
for(ll i=2; i<=n; i++){
double add=f[c[i-1]][c[i]];
for(ll j=0; j<=m&&j<=i; j++){
dp[i][j][0]=min(dp[i-1][j][0]+add,dp[i-1][j][1]+f[d[i-1]][c[i]]*k[i-1]+f[c[i-1]][c[i]]*(1-k[i-1]));
if(j) dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*k[i]+f[c[i-1]][c[i]]*(1-k[i]),dp[i-1][j-1][1]+f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i])+f[c[i-1]][d[i]]*(1-k[i-1])*k[i]+f[d[i-1]][c[i]]*(1-k[i])*k[i-1]+f[d[i-1]][d[i]]*k[i-1]*k[i]);
}
}
for(ll i=0; i<=m; i++) Min=min(Min,min(dp[n][i][0],dp[n][i][1]));
printf("%.2lf",Min);
return 0;
}
```
### BZOJ3270 博物馆
环型点期望例题
对于任意的$x$,$y$,$x \neq y$:
- $x \rightarrow u$,y不动
- $y \rightarrow v$,x不动
- $x \rightarrow u,y \rightarrow v$
- $x,y$都不动
我们用$a_{(x,y),(u,v)}$表示从状态$(x,y)$到状态$(u,v)$的概率。
根据上面的推断,我们得出结论:
当x与u相连或x=u,而且y与v相连或y=v时
$$
a_{(x,y),(u,v)}=\left\{
\begin{aligned}
-1,x=u \space and \space y=v\\
\frac{1-p_v}{degree_v}*p_x,x=u \space and \space y \neq v\\
\frac{1-p_u}{degree_u}*p_y,y=v \space and \space x \neq u\\
\frac{1-p_u}{degree_u}*\frac{1-p_v}{degree_v},x \neq u \space and \space y \neq v\\
\end{aligned}
\right.
$$
结果状态是$a_{id(s,t),endpos}=1$
其他情况$a_{(x,y),(u,v)}$均为0
因为状态里面有环,直接高斯消元
最后输出$a_{id(i,i),endpos}$
我在代码里把endpos用作$n*n+1$
代码:
```cpp
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=30;
ll n,m,s,t,degree[N];
vector<ll> G[N];
double a[N*N][N*N],p[N];
inline ll read(){
ll x=0,tmp=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') tmp=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return tmp*x;
}
inline ll id(ll x,ll y){
return (x-1)*n+y;
}
inline void Gauss(ll n){
for(ll i=1; i<=n; i++){
ll k=i;
for(ll j=i+1; j<=n; j++){
if(fabs(a[k][i])<fabs(a[j][i])) k=j;
}
swap(a[i],a[k]);
double div=a[i][i];
for(ll j=1; j<=n+1; j++) a[i][j]/=div;
for(ll j=1; j<=n; j++){
if(i==j) continue;
div=a[j][i];
for(ll k=1; k<=n+1; k++) a[j][k]-=a[i][k]*div;
}
}
}
int main(){
n=read(); m=read(); s=read(); t=read();
for(ll i=1; i<=n; i++) a[i][i]=1;
while(m--){
ll x=read(),y=read();
G[x].push_back(y);
G[y].push_back(x);
degree[x]++;
degree[y]++;
}
for(ll i=1; i<=n; i++) scanf("%lf",&p[i]);
for(ll x=1; x<=n; x++){
for(ll y=1; y<=n; y++){
if(x==y) a[id(x,y)][id(x,y)]=-1;
else a[id(x,y)][id(x,y)]=p[x]*p[y]-1;
for(ll i=0; i<(ll)G[x].size(); i++){
ll u=G[x][i];
if(u==y) continue;
a[id(x,y)][id(u,y)]=(1-p[u])/degree[u]*p[y];
}
for(ll i=0; i<(ll)G[y].size(); i++){
ll v=G[y][i];
if(x==v) continue;
a[id(x,y)][id(x,v)]=(1-p[v])/degree[v]*p[x];
}
for(ll i=0; i<(ll)G[x].size(); i++){
ll u=G[x][i];
for(ll j=0; j<(ll)G[y].size(); j++){
ll v=G[y][j];
if(u==v) continue;
a[id(x,y)][id(u,v)]=(1-p[u])/degree[u]*(1-p[v])/degree[v];
}
}
}
}
a[id(s,t)][n*n+1]=-1;
Gauss(n*n);
for(ll i=1; i<=n; i++) printf("%.6lf ",a[id(i,i)][n*n+1]); putchar('\n');
return 0;
}
```
### P3232 [HNOI2013]游走
环形边期望例题
我们求出每一条边的期望
首先到了n点之后就不会走出去,所以我们直接把n点作为结束状态
$a_{i,i}=1(1 \leq i <n),a_{1,n}=1$
然后对于所有边,$a_{x,y}-=\frac{1}{degree_y}$
这个可以认为是走过去再走回来的补偿概率
然后跑高斯消元
最后每条边的期望是$\sum \frac{a_{x,n}}{degree_x}+\frac{a_{y,n}}{degree_y}$
最后按边的期望排序,期望小的序号大
代码:
```cpp
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=510,M=125010;
ll n,m,x[M],y[M],degree[N];
double a[N][N],w[M],ans;
inline ll read(){
ll x=0,tmp=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') tmp=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return tmp*x;
}
inline void Gauss(){
for(ll i=1; i<n; i++){
ll k=i;
for(ll j=i+1; j<n; j++){
if(fabs(a[k][i])<fabs(a[j][i])) k=j;
}
swap(a[i],a[k]);
double div=a[i][i];
for(ll j=1; j<=n; j++) a[i][j]/=div;
for(ll j=1; j<n; j++){
if(i==j) continue;
div=a[j][i];
for(ll k=1; k<=n; k++) a[j][k]-=a[i][k]*div;
}
}
}
inline bool cmp(double x,double y){
return x>y;
}
int main(){
n=read(); m=read();
for(ll i=1; i<=m; i++){
x[i]=read(); y[i]=read();
degree[x[i]]++;
degree[y[i]]++;
}
for(ll i=1; i<n; i++) a[i][i]=1;
a[1][n]=1;
for(ll i=1; i<=m; i++){
if(x[i]==n||y[i]==n) continue;
a[x[i]][y[i]]-=1.0/degree[y[i]];
a[y[i]][x[i]]-=1.0/degree[x[i]];
}
Gauss();
for(ll i=1; i<=m; i++) w[i]+=a[x[i]][n]/degree[x[i]]+a[y[i]][n]/degree[y[i]];
sort(w+1,w+1+m,cmp);
for(ll i=1; i<=m; i++) ans+=w[i]*i;
printf("%.3lf",ans);
return 0;
}
```
### P4564 [CTSC2018]假面
有干扰的线性期望例题
这题直接维护期望会非常难理解,由于概率非常好处理,所以维护概率
设$f_{i,j}$表示当前第i个人的血量为j的概率
转移方程只有2中情况:被打了掉血或不掉血。特别的,0血的人不会被打
$
f_{i,j}=\left\{
\begin{aligned}
f_{i,j+1}*p+f_{i,j}*(1-p),j \geq 1\\
f_{i,j+1}*p+f_{i,j},j=0\\
\end{aligned}
\right.
$
对于每一个怪而言:
期望直接用 $\sum$概率\*收益 来求
dp类似01背包的压维,倒着跑
$
dp_j=\left\{
\begin{aligned}
g_i*dp_{j-1}+(1-g_i)*dp_j,j \geq 1\\
(1-g_i)*dp_j,j=0
\end{aligned}
\right.
$
理论上可以出答案了,但跑的太慢,所以要优化。
活着就是血量不为0,概率可以直接得出
$g_i=1-f_{i,0}$
然后继续推
没人死就直接抄:当$g_i=1$时$sum_j=dp_{j+1}$
因为是在不死的人里面选,
$
sum_j=\left\{
\begin{aligned}
\frac{dp_j+sum_{j-1}*g_i}{1-g_i},j \geq 1\\
\frac{dp_j}{1-g_i},j=0\\
\end{aligned}
\right.
$
代码:
```cpp
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=2010,mod=998244353;
ll n,a[N],f[N][N],g[N],dp[N],sum[N];
inline ll read(){
ll x=0,tmp=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') tmp=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return tmp*x;
}
inline void write(ll x){
if(x<0){
putchar('-');
x=-x;
}
ll y=10,len=1;
while(y<=x){
y=(y<<3)+(y<<1);
len++;
}
while(len--){
y/=10;
putchar(x/y+48);
x%=y;
}
}
inline ll ksm(ll x,ll y){
ll ans=1;
while(y){
if(y&1) ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
inline ll inv(ll x){
return ksm(x,mod-2);
}
int main(){
n=read();
for(ll i=1; i<=n; i++){
a[i]=read();
f[i][a[i]]=1;
}
for(ll q=read(); q; q--){
ll op=read();
if(op==0){
ll x=read(),fenzi=read(),fenmu=read();
ll gailv=fenzi*inv(fenmu)%mod;
f[x][0]=(f[x][0]+f[x][1]*gailv%mod)%mod;
for(ll i=1; i<=a[x]; i++) f[x][i]=(f[x][i]*(mod+1-gailv)%mod+f[x][i+1]*gailv%mod)%mod;
}
else if(op==1){
ll m=read();
for(ll i=1; i<=m; i++){
ll x=read();
g[i]=(mod+1-f[x][0])%mod;
}
memset(dp,0,sizeof(dp));
dp[0]=1;
for(ll i=1; i<=m; i++){
for(ll j=i; j>=0; j--){
if(j) dp[j]=(g[i]*dp[j-1]%mod+(mod+1-g[i])%mod*dp[j]%mod)%mod;
else dp[j]=(mod+1-g[i])%mod*dp[j]%mod;
}
}
for(ll i=1; i<=m; i++){
if(!g[i]){
putchar('0'); putchar(' ');
continue;
}
if(g[i]==1){
for(ll j=0; j<m; j++) sum[j]=dp[j+1];
}
else{
ll x=inv((mod+1-g[i])%mod);
sum[0]=dp[0]*x%mod;
for(ll j=1; j<m; j++) sum[j]=(dp[j]+mod-sum[j-1]*g[i]%mod)%mod*x%mod;
}
ll ans=0;
for(ll j=0; j<m; j++) ans=(ans+sum[j]*inv(j+1)%mod)%mod;
write(g[i]*ans%mod); putchar(' ');
}
putchar('\n');
}
else cout<<"FUCK "<<op<<endl;
}
for(ll i=1; i<=n; i++){
ll ans=0;
for(ll j=1; j<=a[i]; j++) ans=(ans+j*f[i][j])%mod;
write(ans); putchar(' ');
}
putchar('\n');
return 0;
}
```