概率与数学期望
我们通过一道题来引入:
绿豆蛙的归宿
题面:
小蝌蚪太可怜了,它又双叒叕找不到妈妈了!
最近青蛙妈妈带小蝌蚪们来到了水上迷宫,青蛙妈妈一再强调“一定要跟紧妈妈”,无奈小蝌蚪太贪玩了,它一会游到这一会游到那,不一会就看不到妈妈了,它很着急,都快哭了,你能帮帮它吗?
迷宫是一个有向无环图,其中包含n个点,m条边,任意两个点互相可达,小蝌蚪游过一条边需要花费一定的时间,
小蝌蚪会从点1出发,妈妈在点n,一个点如果有p条出边,那么小蝌蚪可以随意选择一条路径并且选择每一条路径的概率都是1p,请你帮帮小蝌蚪,算出它找到妈妈一共要游的路径总长期望值。
输入格式:
输入的第一行是两个整数,分别代表图的点数 n 和边数 m。
第 2 到第 m + 1 行,每行有三个整数 u, v, w,代表存在一条从 u 指向 v 长度为 w 的有向边。
输出格式:
输出一行一个实数代表答案,四舍五入保留两位小数。
样例:
input:
4 4
1 2 1
1 3 2
2 3 3
3 4 4
output:
7.00
题意分析:
据说这还是一道板子题…是期望dp的板子题…还是自己太辣鸡,开提高课已经急不可待了
updata:8/19
我是笨比,高中概率统计学了个什么勾八,想了这么长时间才想明白…
首先明白一点:
到达某个结果的期望值 = 这个结果 * 从起始状态到这个状态的概率
什么意思呢?
如图:
我们计算从1号点到3号点的期望距离:
路
径
1.1
−
>
3
:
E
1
=
2
×
1
2
=
1
路
径
2.1
−
>
2
−
>
3
:
E
2
=
1
×
1
2
+
3
×
1
2
=
2
这
里
路
径
2
中
从
1
到
2
概
率
为
1
2
,
但
单
看
从
2
到
3
概
率
就
是
1
,
但
是
从
1
到
3
那
就
是
从
(
1
到
2
的
概
率
)
1
2
×
1
(
2
到
3
的
概
率
)
=
1
2
,
总
结
:
概
率
是
要
叠
乘
的
路径1. 1 - > 3: E_1 = 2 \times \frac{1}{2} = 1 \\ 路径2. 1 -> 2 -> 3 :E_2 = 1 \times \frac{1}{2} + 3\times\frac{1}{2} = 2\\这里路径2中从1到2概率为\frac{1}{2},但单看从2到3概率就是1,\\但是从1到3那就是从(1到2的概率)\frac{1}{2}\times1(2到3的概率)=\frac{1}{2},总结:概率是要叠乘的
路径1.1−>3:E1=2×21=1路径2.1−>2−>3:E2=1×21+3×21=2这里路径2中从1到2概率为21,但单看从2到3概率就是1,但是从1到3那就是从(1到2的概率)21×1(2到3的概率)=21,总结:概率是要叠乘的
这题是有正推和倒推两种写法的。
知道上面的东西之后那么我们就可以来搞正推的写法了
正推:
设:a1, a2, a3 … ak 到 j 的权值为 w1, w2, w3 … wk,
从起点到这k个点的概率为:p1, p2, p3 … pk
每个点的出度为:out1, out2, out3, … , outk
注:这里的1~k个点的从起点的到该点的概率一定是确定的也就是说这个点的概率是被更新完的,即此时这个点的入度为0!
那么就有:
E
(
i
)
:
表
示
从
起
点
到
i
点
的
期
望
距
离
E
(
J
)
=
E
(
1
)
+
w
1
×
p
1
o
u
t
1
+
E
(
2
)
+
w
2
×
p
2
o
u
t
2
+
E
(
3
)
+
w
3
×
p
3
o
u
t
3
+
.
.
.
+
E
(
k
)
+
w
k
×
p
k
o
u
t
k
=
∑
i
=
1
k
E
(
i
)
+
w
i
×
p
i
o
u
t
i
E(i):表示从起点到i点的期望距离\\ E(J) = \frac{E(1) +w_1\times p_1}{out_1} + \frac{E(2) +w_2\times p_2}{out_2} \\+\frac{E(3) +w_3\times p_3}{out_3} + ... +\frac{E(k) +w_k\times p_k}{out_k} \\ =\sum_{i=1}^{k}{ \frac{E(i) +w_i\times p_i}{out_i}}
E(i):表示从起点到i点的期望距离E(J)=out1E(1)+w1×p1+out2E(2)+w2×p2+out3E(3)+w3×p3+...+outkE(k)+wk×pk=i=1∑koutiE(i)+wi×pi
以上即是正推的递推式
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], w[M], idx;
int n, m;
int out[N], in[N];
double f[N], p[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
double topsort()
{
queue<int> q;
for(int i = 1; i <= n; i++)
if(!in[i]) q.push(i), p[i] = 1;
while(q.size())
{
int t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
f[j] += (w[i] * p[t] + f[t]) * 1.0 / out[t] ;
p[j] += p[t] * 1.0 / out[t];
in[j]--;
if(!in[j]) q.push(j);
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) h[i] = -1;
while(m--)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
out[a]++, in[b]++;
}
topsort();
// for(int i = 1; i <= n; i++)
// printf("%d--%.2lf--%.2lf\n", i , f[i], p[i]);
printf("%.2lf", f[n]);
return 0;
}
现在学会了正推,那么咱们来看看逆推,即从终点找到起点
我们令:f(i)表示从i到n的期望距离,那么我们想要的答案即为f(1)
如图:点j通过x1,x2,x3,… , xk到n, 点j到这K个点的距离为:w1, w2, w3 ,… , wk
这K个点到n点的期望距离分别为f1, f2, f3, … , fk
outj:表示j的出度,1/outj:即为j到这K个点的概率
那么我们会有:
f
(
j
)
=
f
1
+
w
1
o
u
t
j
+
f
2
+
w
2
o
u
t
j
+
f
3
+
w
3
o
u
t
j
+
.
.
.
+
f
k
+
w
k
o
u
t
j
=
∑
i
=
1
k
f
i
+
w
i
o
u
t
j
f(j) = \frac{f_1+w_1}{out_j} + \frac{f_2+w_2}{out_j} + \frac{f_3 + w_3}{out_j} + ... + \frac{f_k + w_k}{out_j}\\ =\sum_{i = 1}^{k}{\frac{f_i+w_i}{out_j}}
f(j)=outjf1+w1+outjf2+w2+outjf3+w3+...+outjfk+wk=i=1∑koutjfi+wi
根据递推式我们可以看出我们想求f(j),那我们要求与j相连的点,而求这些相邻的点则要求这些点相邻的点,对你是不是发现,这是个递归回溯的过程,所以我们可以记忆化搜索来解。
关于式子中一个疑惑的解答
咱 们 先 看 j 到 x k 的 这 条 路 径 的 柿 子 : f ( j ) = + f k + w k o u t j 这 里 w k o u t j 我 理 解 不 就 是 j 到 x k 的 概 率 乘 路 径 权 值 嘛 , 但 是 f k o u t j 这 个 x k 的 期 望 也 要 乘 j 的 概 率 呀 ? ? 来 来 来 , 看 这 里 : 咱 们 一 开 始 就 说 了 , 从 某 个 点 到 某 个 点 的 概 率 是 会 叠 乘 的 , 我 们 来 看 看 式 子 中 都 表 示 什 么 含 义 : f ( x k ) : 仅 表 示 从 x k 到 n 的 期 望 距 离 , 不 包 含 j 这 个 点 到 x k 这 个 点 概 率 的 影 响 f ( j ) : 从 j 到 n 的 期 望 距 离 , 而 在 路 径 上 是 会 经 过 x 1 , x 2 , . . . , x k 这 些 点 的 所 以 我 们 在 求 f ( j ) 的 时 候 要 将 j 到 x k 点 概 率 的 影 响 加 到 f ( x k ) 上 面 即 : f ( j ) = + f k + w k o u t j 综 上 : 我 们 使 用 逆 推 记 忆 化 搜 索 的 递 推 式 应 为 : f ( j ) = ( ( f ( 能 到 达 状 态 的 期 望 ) + w ( 能 到 达 状 态 的 权 值 ) ) × P ( j 到 能 到 达 状 态 的 概 率 ) 咱们先看j到x_k的这条路径的柿子:f(j) \overset{\text{+}}{=}\frac{f_k + w_k}{out_j}\\ 这里\frac{w_k}{out_j}我理解不就是j到x_k的概率乘路径权值嘛,\\但是\frac{f_k}{out_j}这个x_k 的期望也要乘j的概率呀??\\ 来来来,看这里:咱们一开始就说了,从某个点到某个点的概率是会叠乘的,我们来看看式子中都表示什么含义:\\f(x_k):仅表示从x_k到n的期望距离,不包含j这个点到x_k这个点概率的影响\\f(j):从j到n的期望距离,而在路径上是会经过x_1,x_2,...,x_k这些点的 \\所以我们在求f(j)的时候要将j到x_k点概率的影响加到f(x_k)上面即:f(j) \overset{\text{+}}{=}\frac{f_k + w_k}{out_j}\\ 综上:我们使用逆推记忆化搜索的递推式应为:\\f(j) = ((f(能到达状态的期望) + w(能到达状态的权值))\times P(j到能到达状态的概率) 咱们先看j到xk的这条路径的柿子:f(j)=+outjfk+wk这里outjwk我理解不就是j到xk的概率乘路径权值嘛,但是outjfk这个xk的期望也要乘j的概率呀??来来来,看这里:咱们一开始就说了,从某个点到某个点的概率是会叠乘的,我们来看看式子中都表示什么含义:f(xk):仅表示从xk到n的期望距离,不包含j这个点到xk这个点概率的影响f(j):从j到n的期望距离,而在路径上是会经过x1,x2,...,xk这些点的所以我们在求f(j)的时候要将j到xk点概率的影响加到f(xk)上面即:f(j)=+outjfk+wk综上:我们使用逆推记忆化搜索的递推式应为:f(j)=((f(能到达状态的期望)+w(能到达状态的权值))×P(j到能到达状态的概率)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], w[M], idx;
int n , m;
int dout[N];
double f[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
double dfs(int u)
{
if(f[u] >= 0) return f[u];
f[u] = 0;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
f[u] += (dfs(j) + w[i])/ dout[u];
}
return f[u];
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) h[i] = -1, f[i] = -1;
int a, b, c;
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
dout[a] ++;
}
f[n] = 0;
dfs(1);
printf("%.2lf", f[1]);
return 0;
}