[P4316 - 绿豆蛙的归宿](P4316 绿豆蛙的归宿 - 洛谷)
题意解读
题目描述
给出张 n 个点 m 条边的有向无环图,起点为 1,终点为 n,每条边都有一个长度,并且从起点出发能够到达所有的点,所有的点也都能够到达终点。
绿豆蛙从起点出发,走向终点。 到达每一个顶点时,如果该节点有 k 条出边,绿豆蛙可以选择任意一条边离开该点,并且走向每条边的概率为1/k。现在绿豆蛙想知道,从起点走到终点的所经过的路径总长度期望是多少?
那么我们可以从题目中读出以下信息:
-
这是一个DAG(有向无环图),也就是说我们每次路径的选择都是无后效性的,因为没有任何一条路径可以到达我们自己的节点
以下是一个DAG的示例

- 这是一道有关于期望的问题,也就是说我们需要一定的数学知识去解决这道题。
- 由于出现了1/k这个系数,所以我们可能需要考虑小数的情况。
分析
那么在题意解读中我们分析出来了两个关键信息:
- 无后效性
- 有关于期望
那么我们自然想到了期望DP的方式去解决这个问题。
那么我们按照以下步骤设计DP
确定状态
划分阶段-根据阶段确定求解顺序
决策选择-动态转移方程
边界条件-起点设置
求解目标
确定状态
首先我们来解释一下期望到底是个什么东西。
假设现在在图上有三个点A,B,C,且这个图是一个DAG
两条路径可以从A->C:
- A->B->C : 6
- A->C : 4
那么我们从A->C的期望值就是(6 + 4) / 2 = 5,即这个公式:
设有n条路径,每条路径的长度为ln:
$$
(l_1+l_2+l_3+...+l_n) / n
$$
由于这是一个期望DP,所以我们开始的时候一定要建反图,也就是把整个DAG反过来:
g[v].push_back({u , w});
cnt[u]++;//出度
ind[u]++;//入度
那么我们的初始化就完成了。
那么来到我们的第二个问题:
f[x]存的到底是啥?
为了方便后续的编码,我们将f[x]
存储的信息表示为x->n的所有期望路径总长。
划分阶段
我们来确定一下这个问题的求解顺序:
- 存图 - 存一个反图,并且将入度(
ind
)和出度(cnt
)初始化完毕 - 拓补排序 - 由于这个问题求解的是在DAG上的最短路径,所以我们需要一个
top_sort
来完成这个任务 - 在拓补排序中设计DP公式 - 根据期望路径长度的计算公式将DP状态转移公式设计完毕
- 输出排序后的
f[1]
- 此时f[1]就是最终答案
根据这个第四步发表一下我自己的看法:
其实如果是单纯的对这个f进行排序的话完全可以用一个priority_queue<int , vector<int> , greater<int> > q
来完成,因为优先队列天然有序
状态转移方程
我们深入浅出的来解释这个问题,先上公式:
$$
f[v] = 1 * cnt[v] / (w + f[u])
$$
分部解释。
-
f[v] , f[u]分别表示什么 : u,v表示的都是节点编号,其中u是v的前驱节点,f[v]表示的信息为v->n的所有期望路径总长。
-
为什么要乘上cnt[v] : 证明如下:
$$
E(y)= p_1x_1+p_2x_2 +...+p_nx_n \
E(x)= p_1(x_1 + w) + p_2(x_2 + w) +...+p_n(x_n + w) = E(y)+ \sum_i^np_i*w = E(y) + w
$$
以上证明为数学证明。
根据以上信息我们得出了状态转移公式:f[v] += 1.0 / cnt[v] * (w + f[u])
由于我们只是单纯的计算期望长度,没有十分复杂的决策管理,又因为边权为正,所以我们拓补排序计算出来的答案一定是最优解。
起点设置
这个我觉得不需要太多的解释,题目已经规定起点为1,终点为n了
求解目标
Code
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'#define TRACE 1
#define tcout TRACE && cout#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);#define int long longconst int INF = 0x3f3f3f3f3f3f3f3f;
const int P = 998244353;
const int N = 1e6 + 10, M = 2e6 + 10;
int n,m;
vector<pair<int , int> > g[N];
int ind[N];//入度
int cnt[N];//出度
double f[N];
void top_sort()//拓补排序
{queue<int> q;q.push(n);f[n] = 0;while(!q.empty()){auto u = q.front();q.pop();for(auto [v , w] : g[u]){f[v] += 1.0 / cnt[v] * (w + f[u]);ind[v]--;if(ind[v] == 0){q.push(v);//若入度归零,说明这条路走完了}}}
}
signed main()
{cin >> n >> m;for(int i = 1;i <= m;i++){int u,v,w;cin >> u >> v >> w;g[v].push_back({u , w});//邻接表存图cnt[u]++;ind[u]++;}top_sort();cout << fixed << setprecision(2);cout << f[1] << endl;return 0;
}