6.7 KiB
一、题目描述
给出一个有向无环的连通图,起点为 1
,终点为 N
,每条边都有一个长度。
数据保证从起点出发能够到达图中所有的点,图中所有的点也都能够到达终点。
绿豆蛙从起点出发,走向终点。
到达每一个顶点时,如果有 K
条离开该点的道路,绿豆蛙可以选择任意一条道路离开该点,并且走向每条路的概率为 1/K
。
现在绿豆蛙想知道,从起点走到终点所经过的路径总长度的 期望 是多少?
输入格式
第一行: 两个整数 N,M
,代表图中有 N
个点、M
条边。
第二行到第 1+M
行: 每行 3
个整数 a,b,c
,代表从 a
到 b
有一条长度为 c
的有向边。
输出格式 输出从起点到终点路径总长度的 期望值,结果四舍五入保留两位小数。
数据范围
1≤N≤10^5,1≤M≤2N
输入样例:
4 4
1 2 1
1 3 2
2 3 3
3 4 4
输出样例:
7.00
二、数学期望
首先明白一点:到达某个结果的期望值 = 这个结果 * 从起始状态到这个状态的概率
Q:
什么意思呢?
如图:

我们计算从1
号点到3
号点的期望距离
路径1
. \displaystyle 1−>3:E_1=2×\frac{1}{2}=1
路径2
. \displaystyle 1−>2−>3:E_2=1×\frac{1}{2}+3×\frac{1}{2}×1=2
这里路径2
中从1
到2
概率为\displaystyle \frac{1}{2}
,但单看从2
到3
概率就是1
,但是从1
到3
那就是从(1
到2
的概率)\displaystyle \frac{1}{2}
×1
(2
到3
的概率)=\displaystyle \frac{1}{2}
。
所以从 点1
到 点3
的数学期望值=1+2=3
总结:概率是叠乘的
本题有 正推 和 倒推 两种写法:
二、正推法

设:
a_1, a_2, a_3 … a_k
到j
的权值为w_1, w_2, w_3 … w_k
,- 从起点到这
k
个点的概率为:p_1, p_2, p_3 … p_k
- 每个点的出度为:
out_1, out_2, out_3, … , out_k
这里的1\sim k
个点的从起点的到该点的概率一定是确定的,也就是说这个点的概率是被更新完的,即此时这个点的入度为0
!
那么就有:
f(i):表示从起点到i点的期望距离
f(j)=\frac{f(1)+w_1\times p_1}{out_1}+\frac{f(2)+w_2\times p_2}{out_2}+\frac{f(3)+w_3\times p_3}{out_3}+...+\frac{f(k)+w_k\times p_k}{out_k}
正推代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
//邻接表
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int n, m; // n个顶点,m条边
int out[N], in[N]; //出度,入度
double f[N], g[N]; // f:数学期望结果 g:概率
void topsort() {
queue<int> q;
//起点为1,起点的概率为100%
q.push(1);
g[1] = 1.0;
f[1] = 0.0;
// DAG,执行拓扑序,以保证计算的顺序正确,确保递归过程中,前序数据都已处理完毕
while (q.size()) {
auto u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i]) { //枚举的是每边相邻边
int j = e[i]; //此边,一端是t,另一端是j
//此边边条w[i]
f[j] += (f[u] + w[i] * g[u]) / out[u];
g[j] += g[u] / out[u]; // p[j]也需要概率累加
//拓扑序的标准套路
in[j]--;
if (!in[j]) q.push(j);
}
}
}
int main() {
//初始化邻接表
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
//维护出度,入度
out[a]++, in[b]++;
}
//拓扑序
topsort();
//正向递推,输出结果,保留两位小数
printf("%.2lf", f[n]);
return 0;
}
三、倒推法
现在学会了正推,来看看 逆推,即 从终点找到起点

设 f[x]
表示结点 x
走到终点所经过的路径的期望长度。显然 f[n]=0
,最后要求 f[1]
。
一般来说,初始状态确定时可用顺推,终止状态确定时可用逆推。
设 x
出发有 k
条边,分别到达 y_1,y_2...y_k
,边长分别为 z_1,z_2...z_k
,根据数学期望的定义和性质,有:
f[x]=\frac 1 k\times (f[y_1]+z_1)+\frac 1 k\times (f[y_2]+z_2)+...+\frac 1 k\times (f[y_k]+z_k)=\frac 1 k \times \sum_{i=1}^k(f[y_i]+z_i)
根据设定已经确定是能够到达 n
点了,概率为 1
。
f[n]
已知,需要求解 f[1]
,建立 反向图,按照 拓扑序 求解。
倒推代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = N << 1;
int n, m;
int in[N], g[N]; //入度,入度的备份数组,原因:in在topsort中会不断变小受破坏
double f[N];
//链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void topsort() {
queue<int> q;
q.push(n);
f[n] = 0; // n到n的距离期望是0
while (q.size()) {
int u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i]) { //枚举每条入边(因为是反向图)
int j = e[i];
f[j] += (f[u] + w[i]) / g[j];
in[j]--;
if (in[j] == 0) q.push(j);
}
}
}
int main() {
memset(h, -1, sizeof(h));
cin >> n >> m;
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(b, a, c); //反向图,计算从n到1
in[a]++; //入度
g[a] = in[a]; //入度数量
}
topsort();
printf("%.2lf\n", f[1]);
return 0;
}