You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 217. 绿豆蛙的归宿

一、题目描述

给出一个有向无环的连通图,起点为 1 ,终点为 N,每条边都有一个长度。

数据保证从起点出发能够到达图中所有的点,图中所有的点也都能够到达终点。

绿豆蛙从起点出发,走向终点。

到达每一个顶点时,如果有 K 条离开该点的道路,绿豆蛙可以选择任意一条道路离开该点,并且走向每条路的概率为 1/K

现在绿豆蛙想知道,从起点走到终点所经过的路径总长度的 期望 是多少?

输入格式 第一行: 两个整数 NM,代表图中有 N 个点、M 条边。

第二行到第 1+M 行: 每行 3 个整数 a,b,c,代表从 ab 有一条长度为 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中从12概率为\displaystyle \frac{1}{2},但单看从23概率就是1,但是从13那就是从(12的概率)\displaystyle \frac{1}{2}×1(23的概率)=\displaystyle \frac{1}{2}。 

所以从 点1 到 点3 的数学期望值=1+2=3

总结:概率是叠乘的

本题有 正推倒推 两种写法:

二、正推法

设:

  • a_1, a_2, a_3 … a_kj 的权值为 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;
}