Label
DAG转化成拓扑序后逆向DP
Description
给定一张nnn个点mmm条边、起点为111终点为nnn的DAG,每条边均有一个长度,且从起点出发可到达所有的点,所有的点也能到达终点。
绿豆蛙从起点出发走向终点,到达每一个顶点时,若有kkk条离开该点的道路,它会等概率地选择任意一条道路离开该点,即走向每条路的概率均为1k\frac{1}{k}k1。求它从起点到终点所经过地路径的期望长度为多少。
数据范围:1≤n≤105,1≤m≤2n1\leq n \leq 10^5,1\leq m \leq 2n1≤n≤105,1≤m≤2n,答案四舍五入后保留两位小数输出。
Solution
(友情提示:本文中(u,v,w)代表从点u通向点v的,边权为w的有向边)
顺推—一个不好搞的思路
用dp[i]dp[i]dp[i]表示从点111到点iii所经过的路径长度的期望,那么:
dp[i]=∑pk(dp[u[k]+w[k]])dp[i]=\sum p_k(dp[u[k]+w[k]])dp[i]=∑pk(dp[u[k]+w[k]]),其中pkp_kpk为走编号为k的边的概率,但问题来了:原题意中说的是“等概率地走离开某点的所有道路”,所以所有到达某点的道路走的概率着实不好算(有一种经典的错误思路为dp[i]=∑1outu[k](dp[u[k]]+w[k])dp[i]=\sum\frac{1}{ out_{u[k]}}(dp[u[k]]+w[k])dp[i]=∑outu[k]1(dp[u[k]]+w[k])(out[u[k]]out[u[k]]out[u[k]]为点u[k]u[k]u[k]的出度),但∑1outu[k]\sum\frac{1}{ out_{u[k]}}∑outu[k]1显然不等于1,此式不符合期望性质);故考虑转换思路。
逆推
在期望题中,逆推的使用条件往往是**“终止状态确定时可用逆推”,那么我们考虑想一个终止状态确定的状态**。
此处状态与顺推相反,dp[i]dp[i]dp[i]不再表示从点111到点iii所经过的路径长度的期望,而是从点iii到点nnn所经过的路径长度的期望。知道了状态,我们很容易列出符合期望公式E(x)=∑pixiE(x)=\sum p_i x_iE(x)=∑pixi的dpdpdp式:
dp[x]=∑i=1out[x]1out[x](dp[v[i]]+w[i])dp[x]=\sum_{i=1}^{out[x]}\frac{1}{out[x]}(dp[v[i]]+w[i])dp[x]=∑i=1out[x]out[x]1(dp[v[i]]+w[i])
(dp[n]=0)(dp[n]=0)(dp[n]=0)
其中out[x]out[x]out[x]为点xxx的出度。
由于是逆推,故可以在拓扑排序过程中实现答案统计。
Code
#include<cstdio>
#include<iostream>
#define ri register int
using namespace std;
const int MAXN=200020;
int n,m,top,u[MAXN],v[MAXN],fst[MAXN],nxt[MAXN],out[MAXN],dfn[MAXN];
double w[MAXN],dp[MAXN];
void topsort(int x)
{
dfn[x]=1;
for(ri k=fst[x];k>0;k=nxt[k])
{
if(dfn[v[k]])//此处if顺序不能写反!
{
dp[x]+=dp[v[k]]+w[k];
continue;
}
topsort(v[k]);
dp[x]+=dp[v[k]]+w[k];//统计出度点的答案
}
if(out[x]!=0) dp[x]/=out[x];
}
int main()
{
scanf("%d%d",&n,&m);
top=n;
for(ri i=1;i<=m;i++)
{
scanf("%d%d%lf",&u[i],&v[i],&w[i]);
nxt[i]=fst[u[i]],fst[u[i]]=i;
++out[u[i]];
}
topsort(1);
printf("%.2f",dp[1]);//.xf自带四舍五入功能
return 0;
}
这篇博客讲解了如何将DAG图转化为拓扑排序后,利用逆推动态规划求解从终点到起点的路径长度期望问题。通过构建从终点到起点的期望状态转移方程,避免了传统顺推方法中的复杂概率计算,适用于等概率选择路径的情况。
272

被折叠的 条评论
为什么被折叠?



