从期望已知的地方开始算起,逐步递推。
公式的依据,期望的线性性质 E ( a X + b Y ) = a E ( X ) + b E ( Y ) \mathbb{E}(aX+bY) = a\mathbb{E}(X) + b\mathbb{E}(Y) E(aX+bY)=aE(X)+bE(Y)。
进一步可以推导出这样的结论
E
(
X
)
=
∑
i
P
(
X
∣
Y
i
)
⋅
E
(
Y
i
)
\mathbb{E}(X) = \sum_{i} P(X|Y_i) \cdot \mathbb{E}(Y_i)
E(X)=i∑P(X∣Yi)⋅E(Yi)
POJ2096 Collecting Bugs
题目大意:一个软件有s个子系统,会产生 n 种 bug。某人一天发现一个 bug,这个 bug 属于某种 bug 分类,也属于某个子系统。每个 bug 属于某个子系统的概率是 1/s,属于某种 bug 分类的概率是1/n。求发现 n 种 bug,且 s 个子系统都找到 bug 的期望天数。
#include <cstdio>
using namespace std;
const int N = 1010;
double f[N][N];
int main()
{
int n, s;
scanf("%d%d", &n, &s);
for(int i = n; ~i; i--)
{
for(int j = s; ~j; j--)
{
if(i == n && j == s) continue;
double p1, p2, p3, p4;
p1 = 1.0 * i * j / n / s;
p2 = 1.0 * (n - i) * j / n / s;
p3 = 1.0 * i * (s - j) / n / s;
p4 = 1.0 * (n - i) * (s - j) / n / s;
f[i][j] = p2 * f[i + 1][j] + p3 * f[i][j + 1] + p4 * f[i + 1][j + 1] + 1;
f[i][j] /= 1 - p1;
}
}
printf("%.4lf\n", f[0][0]);
}
「NOIP2016」换教室
题目大意:牛牛要上 n 个时间段的课,第 i 个时间段在 ci 号教室,可以申请换到 di 号教室,申请成功的概率为 pi,至多可以申请 m 节课进行交换。第 i 个时间段的课上完后要走到第 i+1 个时间段的教室,给出一张图 v 个教室 e 条路,移动会消耗体力,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,也就是求出最小的期望路程和。
明白了一件事,DP方程的设计代表一种决策的选择。
最开始我设计的方程是f[i][j]表示前i个教室,申请了j次的期望,发现不够用,于是新增了一维f[i][j][0/1]表示前i个教室,申请了j次,最后在0(原教室),1(新教室)的期望。这个时候的DP状态转移方程是寸步难行。因为如果要求f[i][j][0],有可能是没申请,也有可能是在i-1的c课室或者d课室申请失败过来的。这个时候就很难描述,因为“申请”和“没申请”这个事件并不是概率可以求解的,应该是通过类似取min来决定的。
后来看题解,设的状态是f[i][j][0/1]表示前i个教室,申请了j次,最后一间课室申请了(1),或者没(0)。这样子就能把状态转移说明白了。申请了那么有p[i]的概率在d课室,有(1-p[i])的概率在c课室。f[i][j][0]可以从f[i-1][j][0]或者f[i-1][j][1]转移过来,哪个优就选哪个,这个选择体现的是考虑i-1处是申请优,还是不申请优,决策就做出来了。
所以f这题设的是决策,不是状态。设状态的话,决策的概率是没法算的。设决策,才能绕开决策的概率。
细节,注意DP方程中那些不会填写的但是会被访问的量,注意给它们合理的赋初值。
就这题而言,先folyd求两两距离,然后概率DP即可。
转移方程见代码。
#include <bits/stdc++.h>
using namespace std;
const int N = 2010, V = 305;
int c[N], d[N];
int w[V][V];
double p[N], f[N][N][2];
int main()
{
memset(w, 0x3f, sizeof(w));//debug 语句不能放下面的呀
int n, m, v, e;
scanf("%d%d%d%d", &n, &m, &v, &e);
for(int i = 1; i <= n; i++) scanf("%d", &c[i]);
for(int i = 1; i <= n; i++) scanf("%d", &d[i]);
for(int i = 1; i <= n; i++) scanf("%lf", &p[i]);
for(int i = 1; i <= e; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
w[x][y] = w[y][x] = min(w[x][y], z);
}
for(int i = 1; i <= v; i++) w[i][i] = 0;//debug
for(int k = 1; k <= v; k++)
for(int i = 1; i <= v; i++)
for(int j = 1; j < i; j++)
w[i][j] = w[j][i] = min(w[i][j], w[i][k] + w[k][j]);
for(int i = 1; i <= n; i++)
for(int j = 0; j <= m; j++) f[i][j][0] = f[i][j][1] = 1e9;
f[1][0][0] = f[1][1][1] = 0; //debug
for(int i = 2; i <= n; i++)
{
for(int j = 0; j <= min(i, m); j++) //debug j = 1
{
f[i][j][0] = min(f[i - 1][j][0] + w[c[i - 1]][c[i]],
f[i - 1][j][1] + p[i - 1] * w[d[i - 1]][c[i]] + (1 - p[i - 1]) * w[c[i - 1]][c[i]]);
if(j > 0) f[i][j][1] = min(f[i - 1][j - 1][0] + p[i] * w[c[i - 1]][d[i]] + (1 - p[i]) * w[c[i - 1]][c[i]],
f[i - 1][j - 1][1] +
p[i - 1] * p[i] * w[d[i - 1]][d[i]] +
(1 - p[i - 1]) * p[i] * w[c[i - 1]][d[i]] +
p[i - 1] * (1 - p[i]) * w[d[i - 1]][c[i]] +
(1 - p[i - 1]) * (1 - p[i]) * w[c[i - 1]][c[i]]);
}
}
double ans = 1e9;
for(int i = 0; i <= m; i++)
{
ans = min(ans, f[n][i][0]);
ans = min(ans, f[n][i][1]);
}
printf("%.2lf\n", ans);
return 0;
}
OSU系列
每一个位置有 p i p_i pi的概率为
O
,否则为X
。对于一段连续的O
,如果有n个,得分为 f ( n ) f(n) f(n)。求全部下来的期望得分。
E ( S i ) \mathbb{E}(S_i) E(Si)表示操作了i次后的期望得分
E ( A i ) \mathbb{E}(A_i) E(Ai)表示操作了i次后的期望连击数
一: f ( n ) = n f(n)=n f(n)=n
考虑最简化的一个问题,
f
(
n
)
=
n
f(n)=n
f(n)=n。即连续n个O
的得分为n。
E
(
S
i
)
=
p
i
⋅
(
E
(
S
i
−
1
)
+
1
)
+
(
1
−
p
i
)
⋅
E
(
S
i
−
1
)
=
E
(
S
i
−
1
)
+
p
i
\begin{aligned} \mathbb{E}(S_i)&=p_i\cdot(\mathbb{E}(S_{i-1})+1)+(1-p_i)\cdot \mathbb{E}(S_{i-1}) \\ &= \mathbb{E}(S_{i-1})+p_i \end{aligned}
E(Si)=pi⋅(E(Si−1)+1)+(1−pi)⋅E(Si−1)=E(Si−1)+pi
这个式子很好理解吧,如果
i
i
i这个位置出现了O
,就能贡献1的价值。
二: f ( n ) = n 2 f(n)=n^2 f(n)=n2
E ( S i ) = p i ⋅ E [ S i − 1 + f ( E ( A i − 1 ) + 1 ) − f ( E ( A i − 1 ) ) ] + ( 1 − p i ) ⋅ E ( S i − 1 ) = p i ⋅ E ( S i − 1 + 2 A i − 1 + 1 ) + ( 1 − p i ) ⋅ E ( S i − 1 ) = p i ⋅ E ( S i − 1 ) + p i ⋅ E ( 2 A i − 1 + 1 ) + ( 1 − p i ) ⋅ E ( S i − 1 ) = p i ⋅ E ( 2 A i − 1 + 1 ) + E ( S i − 1 ) \begin{aligned} \mathbb{E}(S_i) &= p_i\cdot \mathbb{E}[S_{i-1} + f(\mathbb{E}(A_{i-1})+1) - f(\mathbb{E}(A_{i-1}))] + (1-p_i)\cdot \mathbb{E}(S_{i-1}) \\ &=p_i\cdot \mathbb{E}(S_{i-1} + 2A_{i-1} + 1) + (1-p_i)\cdot \mathbb{E}(S_{i-1}) \\ &=p_i\cdot \mathbb{E}(S_{i-1}) + p_i\cdot \mathbb{E}(2A_{i-1} + 1) + (1-p_i)\cdot \mathbb{E}(S_{i-1}) \\ &=p_i\cdot \mathbb{E}(2A_{i-1} + 1) + \mathbb{E}(S_{i-1}) \end{aligned} E(Si)=pi⋅E[Si−1+f(E(Ai−1)+1)−f(E(Ai−1))]+(1−pi)⋅E(Si−1)=pi⋅E(Si−1+2Ai−1+1)+(1−pi)⋅E(Si−1)=pi⋅E(Si−1)+pi⋅E(2Ai−1+1)+(1−pi)⋅E(Si−1)=pi⋅E(2Ai−1+1)+E(Si−1)
这里面的 f ( E ( A i − 1 ) + 1 ) − f ( E ( A i − 1 ) ) f(\mathbb{E}(A_{i-1})+1) - f(\mathbb{E}(A_{i-1})) f(E(Ai−1)+1)−f(E(Ai−1)),我想表达的是经过 i − 1 i-1 i−1次操作以后有 A i − 1 A_{i-1} Ai−1的连击数,得分为 A i − 1 2 A_{i-1}^2 Ai−12。如果接下来又发生连击了,那么得分会变为 ( A i − 1 + 1 ) 2 (A_{i-1} + 1)^2 (Ai−1+1)2,额外贡献即为两者作差,即 2 A i − 1 + 1 2A_{i-1}+1 2Ai−1+1。
所以接下来的关键求
E
(
A
i
)
\mathbb{E}(A_i)
E(Ai),连击数是好求的
E
(
A
i
)
=
p
i
⋅
E
(
A
i
−
1
+
1
)
\mathbb{E}(A_i) = p_i\cdot \mathbb{E}(A_{i-1} + 1)
E(Ai)=pi⋅E(Ai−1+1)
所以这个问题就解决了。
三: f ( n ) = n 3 f(n) = n^3 f(n)=n3
E ( S i ) = p i ⋅ E [ S i − 1 + E ( A i − 1 + 1 ) 3 − E ( A i − 1 ) 3 ] + ( 1 − p i ) ⋅ E ( S i − 1 ) = p i ⋅ E [ 3 A i − 1 2 + 3 A i − 1 + 1 ] + E ( S i − 1 ) = p i ⋅ [ 3 E ( A i − 1 2 ) + 3 E ( A i − 1 ) + 1 ] + E ( S i − 1 ) \begin{aligned} \mathbb{E}(S_i) &= p_i\cdot \mathbb{E}[S_{i-1} + \mathbb{E}(A_{i-1}+1)^3 - \mathbb{E}(A_{i-1})^3] + (1-p_i)\cdot \mathbb{E}(S_{i-1}) \\ &= p_i\cdot \mathbb{E}[3A_{i-1}^2 + 3A_{i-1} + 1]+ \mathbb{E}(S_{i-1}) \\ &= p_i\cdot [3\mathbb{E}(A_{i-1}^2) + 3\mathbb{E}(A_{i-1}) + 1]+ \mathbb{E}(S_{i-1}) \end{aligned} E(Si)=pi⋅E[Si−1+E(Ai−1+1)3−E(Ai−1)3]+(1−pi)⋅E(Si−1)=pi⋅E[3Ai−12+3Ai−1+1]+E(Si−1)=pi⋅[3E(Ai−12)+3E(Ai−1)+1]+E(Si−1)
所以关键是求出 E ( A i 2 ) \mathbb{E}(A_i^2) E(Ai2)。
E ( A i 2 ) = p i ⋅ E [ ( A i − 1 + 1 ) 2 ] = p i ⋅ [ E ( A i − 1 2 ) + 2 E ( A i − 1 ) + 1 ] \begin{aligned} \mathbb{E}(A_i^2) &= p_i \cdot \mathbb{E}[(A_{i-1} + 1)^2] \\ &=p_i \cdot [\mathbb{E}(A_{i-1}^2) + 2\mathbb{E}(A_{i-1}) + 1] \end{aligned} E(Ai2)=pi⋅E[(Ai−1+1)2]=pi⋅[E(Ai−12)+2E(Ai−1)+1]
这样就做完了。
然后可能有人会分不清楚, E ( A i 2 ) \mathbb{E}(A_i^2) E(Ai2)好像是在第二题里已经求过了。第二题里面,我们已经求出了在平方下的期望得分,那个东西应该就是了吧?
答案是否定的。第二题求的叫经过i次操作后的期望得分,而
E
(
A
i
2
)
\mathbb{E}(A_i^2)
E(Ai2)是经过i次操作后连击数的平方的期望。连击数的平方的期望与第二题的得分不是一回事,连击数的平方的期望不会去考虑当前位置产生X
下的进一步期望;而得分期望是要把前一步的期望继承过来的。区别还是很明显的。
第二题代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
double a[N], f[N], e[N];
void solve()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lf", &a[i]);
for(int i = 1; i <= n; i++)
{
f[i] = a[i] * (f[i - 1] + 1);
e[i] = e[i - 1] + a[i] * (2 * f[i -1] + 1);
}
printf("%.15lf\n", e[n]);
}
int main()
{
int T = 1;
//scanf("%d", &T);
while(T--) solve();
}
第三题代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
double a[N], f[N], f2[N], e[N];
void solve()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lf", &a[i]);
for(int i = 1; i <= n; i++)
{
f[i] = a[i] * (f[i - 1] + 1);
f2[i] = a[i] * (f2[i - 1] + 2 * f[i - 1] + 1);
e[i] = a[i] * (3 * f2[i - 1] + 3 * f[i - 1] + 1) + e[i - 1];
}
printf("%.1lf\n", e[n]);
}
int main()
{
int T = 1;
//scanf("%d", &T);
while(T--) solve();
}