「算法笔记」期望 DP 入门
一、数学期望
1. 由来
在 \(17\) 世纪,有一个赌徒向法国著名数学家帕斯卡挑战,给他出了一道题目:甲乙两个人赌博,他们两人获胜的机率相等,比赛规则是先胜三局者为赢家,一共进行五局,赢家可以获得 \(100\) 法郎的奖励。当比赛进行到第四局的时候,甲胜了两局,乙胜了一局,这时由于某些原因中止了比赛,那么如何分配这 \(100\) 法郎才比较公平?
甲输掉后两局的可能性只有 \(\frac{1}{2} \times \frac{1}{2}=\frac{1}{4}\),也就是说甲赢得后两局或后两局中任意赢一局的概率为 \(1-\frac{1}{4}=\frac{3}{4}\),甲有 \(75\%\) 的期望获得 \(100\) 法郎;而乙期望赢得 \(100\) 法郎就得在后两局均击败甲,乙连续赢得后两局的概率为 \(\frac{1}{2}\times \frac{1}{2}=\frac{1}{4}\),即乙有 \(25\%\) 的期望获得 \(100\) 法郎奖金。
可见,虽然不能再进行比赛,但依据上述可能性推断,甲乙双方最终胜利的客观期望分别为 \(75\%\) 和 \(25\%\),因此甲应分得奖金的 \(100\times 75\%=75\) (法郎),乙应分得奖金的的 \(100×25\%=25\) (法郎)。这个故事里出现了“期望”这个词,数学期望由此而来。(¿)
(摘自百度百科)
2. 定义
数学期望是实验中每次可能结果的概率乘以其结果的总和,它的定义式为:
其中 \(i\) 是所有可能发生的事件,\(x_i\) 为事件的权值,\(p_i\) 为事件发生的概率。
3. 性质
设 \(C\) 为一个常数, \(X\) 和 \(Y\) 是两个随机变量。以下是数学期望的重要性质:
- 1. \(E(C)=C\)
- 2. \(E(CX)=CE(X)\)
- 3. 期望的 线性性:\(E(X+Y)=E(X)+E(Y)\)
- 4. 当 \(X\) 和 \(Y\) 相互独立时,\(E(XY)=E(X)E(Y)\)
二、例题
1. SP1026 FAVDICE - Favorite Dice
题目大意:一个 \(n\) 面的骰子,求期望掷几次能使得每一面都被掷到。\(n\leq 6000\)。
Solution:
结论:\(1+\frac{n}{n-1}+\frac{n}{n-2}+...+n=\sum_{i=1}^n \frac{n}{i}\)
令 \(f_i\) 表示已经掷出 \(i\) 个不同的面,还期望掷多少次能使得每一面都被掷到。
显然 \(f_n=0\)。
对于所有 \(f_i\ (i\neq n)\),有两种情况:
- 有 \(\frac{i}{n}\) 的概率掷到重复的面,则还需掷 \(f_i\) 次。
- 有 \(\frac{n-i}{n}\) 的概率掷到新的面,则还需掷 \(f_{i+1}\) 次。
\(f_i=(\frac{i}{n} f_i+\frac{n-i}{n} f_{i+1})+1\)
整理得,\(f_i=f_{i+1}+\frac{n}{n-i}\)
因此 \(Ans=f_0=\sum\limits_{i=n-1}^{0} \frac{n}{n-i}=\sum_{i=1}^n \frac{n}{i}\)
#include<bits/stdc++.h> #define int long long using namespace std; int t,n; double ans; signed main(){ scanf("%lld",&t); while(t--){ scanf("%lld",&n),ans=0; for(int i=1;i<=n;i++) ans+=1.0*n/i; printf("%.2lf\n",ans); } return 0; }
2. BZOJ 1419 Red is good
题目大意:桌面上有 \(R\) 张红牌和 \(B\) 张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到 \(1\) 美元,黑牌则付出 \(1\) 美元。可以随时停止翻牌,在最优策略下平均能得到多少钱。\(0\leq R,B \leq 5000\)。
Solution:
令 \(f_{r,b}\) 表示剩下 \(r\) 张红牌、\(b\) 张黑牌的期望收益。
首先考虑边界情况。
当 \(r=0\),即目前已经没有红牌时,停止翻牌,则 \(f_{r,b}=0\)。当 \(b=0\),即剩下的牌没有黑牌时,肯定会把剩下的红牌全部翻掉,则 \(f_{r,b}=r\)。
讨论其他情况。
有 \(\frac{r}{r+b}\) 的概率翻到一张红牌,并带来 \(1\) 的收益;有 \(\frac{b}{r+b}\) 的概率翻到一张黑牌,并带来 \(1\) 的损失。
那么显然 \(f_{r,b}=\max(0,\frac{r}{r+b}(1+f_{r-1,b})+\frac{b}{r+b}(-1+f_{r,b-1})\)。因为我们采用的是最优策略,所以当此时的局面已经不足以带来大于 \(0\) 的期望收益时,应该停止翻牌,所以最后还要和 \(0\) 取 \(max\)。
由于卡空间,需要使用滚动数组。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5010; int n,m; double f[2][N],ans; signed main(){ scanf("%lld%lld",&n,&m); for(int r=1;r<=n;r++){ f[r%2][0]=1.0*r; for(int b=1;b<=m;b++) f[r%2][b]=max(0.0,1.0*r/(r+b)*(1+f[(r-1)%2][b])+1.0*b/(r+b)*(-1+f[r%2][b-1])); } printf("%.6lf\n",1.0*floor(f[n%2][m]*1e6)/1e6); return 0; }
3. BZOJ 4318 OSU!
题目大意:给出一串数,每个数字有 \(a_i\) 的概率是 \(\text{O}\),这串数字的分数定义为每一段极长连续的 \(\text{O}\) 的长度的立方和,\(\text{OXXOO}\) 的分数就是 \(1^3+2^3=9\),求期望分数。\(N\leq 10^5\)。
Solution:
令 \(y_i\) 表示到 \(i\) 为止连续打出多少个 \(\text{O}\)。
有 \(p=a_i\) 的概率打出 \(\text{O}\),则有 \(p\) 的概率 \(y_i=y_{i-1}+1\);有 \(1-p\) 的概率打出的不是 \(\text{O}\),则有 \(1-p\) 的概率 \(y_i=0\)。
\(E(y_i)=E(p\times (y_{i-1}+1))=p\times (E(y_{i-1})+1)\)
因为 \((x+1)^3=x^3+3x^2+3x+1\),所以还需要维护平方和。每打出 \(1\) 个 \(\text{O}\),答案 \((x+1)^3\) 比原答案 \(x^3\) 相比多了 \(3x^2+3x+1\)。
\(E({y_i}^2)=E(p\times (y_{i-1}+1)^2)=p\times (E({y_{i-1}}^2)+2\times E(y_{i-1})+1)\)
可以分别用 \({f_1}_i,{f_2}_i\) 维护 \(E(y_i),E({y_i}^2)\)。
\({f_1}_{i}=p\times ({f_1}_{i-1}+1)\)
\({f_2}_{i}=p\times({f_2}_{i-1}+2\times {f_1}_{i-1}+1)\)
那么 \({ans}_i={ans}_{i-1}+p\times (3\times {f_2}_{i-1}+3\times {f_1}_{i-1}+1)\),最终的答案为 \({ans}_{n}\)。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+5; int n; double a[N],f1[N],f2[N],ans[N]; signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lf",&a[i]); for(int i=1;i<=n;i++){ f1[i]=a[i]*(f1[i-1]+1); f2[i]=a[i]*(f2[i-1]+2*f1[i-1]+1); ans[i]=ans[i-1]+a[i]*(3*f2[i-1]+3*f1[i-1]+1); } printf("%.1lf\n",ans[n]); return 0; }
4. HDU 4035 Maze
题目大意:有一个树形的迷宫,有 \(N\) 个房间以及 \(N-1\) 条通道将它们连通,一开始在 \(1\) 号房间,每进入一个房间 \(i\),有 \({kill}_i\) 的概率被陷阱杀死回到房间 \(1\),有 \(s_i\) 的概率找到出口逃离迷宫,如果没有找到出口也没有被杀,那么就在与该房间相连的通道中等概率随机选一条走,求逃离迷宫所需要走的通道数的期望值。(如果不能逃离输出impossible
)。\(T\leq 30,N\leq 10^4\)。
Solution:
令 \(f_i\) 表示目前在节点 \(i\) 上的期望步数。
对于任意一个节点 \(u\),\(f_u={kill}_u f_1+s_u\times 0+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v} (f_v+1)\)(节点 \(v\) 与节点 \(u\) 相连)。
把 \(s_u\times 0\) 这一项省去,再把最后一项拆出来,则 \(f_u={kill}_u f_1+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v} f_v+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v} 1)\),也就是 \(f_u={kill}_u f_1+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v\in {fa_u}} f_{v}+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v\in {son_u}} f_v+(1-{kill}_u-s_u)\)。
注意到:1. 对于任意一个节点 \(u\),式子中都有 \(f_1\)。 2. 如果 \(u\) 为叶子节点,那么 \(v\) 只有 \(u\) 的父亲(叶子节点没有儿子)。
可以发现,叶子节点的式子可以表示成:\(f_u=a_u\times f_1+b_u\times f_{{fa}_{u}}+c_u\),其中 \(a_u\)、\(b_u\) 以及 \(c_u\) 都是常数。
设 \(u\) 是 \(i\) 的父亲,将 \(f_i=a_i\times f_1+b_i\times f_u+c_i\) 代入最开始的那个式子得:
\(f_u={kill}_u f_1+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v\in {fa_u}} f_{v}+\frac{1-{kill}_u-s_u}{{deg}_u}\sum(a_i\times f_1+b_i\times f_u+c_i)+(1-{kill}_u-s_u)\)
那么:
\((1-\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum b_i)\times f_u=({kill}_u+\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum a_i)\times f_1\)
\(+\frac{1-{kill}_u-s_u}{{deg}_u}\sum\limits_{v\in{fa_u}} f_v+(1-{kill}_u-s_u+\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum c_i)\)。
整理一下式子,可以得到:
\(\displaystyle f_u=\frac{({kill}_u+\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum a_i)}{1-\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum b_i}\times f_1\)
\(\displaystyle+\frac{\frac{1-{kill}_u-s_u}{{deg}_u}}{1-\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum b_i}f_{{fa}_u}+\frac{(1-{kill}_u-s_u+\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum c_i)}{1-\frac{1-{kill}_u-s_u}{{deg}_u}\times \sum b_i}\)
把这个式子和 \(f_u=a_u\times f_1+b_u\times f_{{fa}_{u}}+c_u\) 对比,可以发现,所有的式子都能和叶子节点的式子一样被表示成 \(f_u=a_u\times f_1+b_u\times f_{{fa}_{u}}+c_u\) 的形式。
这意味着我们能从叶子节点一直往上推,到根节点的时候就能得到答案。
\(f_1=a_1\times f_1+b_1\times 0+c_1\),所以 \(f_1=\frac{c_1}{1-a_1}\)。\(a_1=1\) 时无解。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e4+5; int t,n,m,x,y,tot,cnt,hd[N],to[N<<1],nxt[N<<1]; double k[N],s[N],a[N],b[N],c[N]; void add(int x,int y){ to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt; } void dfs(int x,int fa){ double num=0,ka=0,kb=0,kc=0,p=1.0-k[x]-s[x]; a[x]=b[x]=c[x]=0; for(int i=hd[x];i;i=nxt[i]){ int y=to[i]; if(y!=fa) ++num,dfs(y,x),ka+=a[y],kb+=b[y],kc+=c[y]; } if(x!=1) ++num; if(num==1&&x!=1) a[x]=k[x],b[x]=1.0-k[x]-s[x],c[x]=b[x]; //叶子节点 else a[x]=(k[x]+p/num*ka)/(1-p/num*kb),b[x]=(p/num)/(1-p/num*kb),c[x]=(p+p/num*kc)/(1-p/num*kb); //非叶子节点 } signed main(){ scanf("%lld",&t); while(t--){ cnt=0,memset(hd,0,sizeof(hd)); scanf("%lld",&n); for(int i=1;i<n;i++){ scanf("%lld%lld",&x,&y); add(x,y),add(y,x); } for(int i=1;i<=n;i++){ scanf("%lf%lf",&k[i],&s[i]); k[i]/=100,s[i]/=100; } dfs(1,0); if(fabs(1-a[1])<1e-9) printf("Case %lld: impossible\n",++tot); //卡精度 else printf("Case %lld: %.6lf\n",++tot,c[1]/(1-a[1])); } return 0; }