洛谷 P4316 绿豆蛙的归宿
题目背景
随着新版百度空间的上线,Blog宠物绿豆蛙完成了它的使命,去寻找它新的归宿。
题目描述
给出一个有向无环图,起点为1终点为N,每条边都有一个长度,并且从起点出发能够到达所有的点,所有的点也都能够到达终点。绿豆蛙从起点出发,走向终点。 到达每一个顶点时,如果有K条离开该点的道路,绿豆蛙可以选择任意一条道路离开该点,并且走向每条路的概率为 1/K 。 现在绿豆蛙想知道,从起点走到终点的所经过的路径总长度期望是多少?
输入格式
第一行: 两个整数 N M,代表图中有N个点、M条边 第二行到第 1+M 行: 每行3个整数 a b c,代表从a到b有一条长度为c的有向边
输出格式
从起点到终点路径总长度的期望值,四舍五入保留两位小数。
题意翻译
「Poetize3」
输入输出样例
输入 #1复制
输出 #1复制
说明/提示
对于20%的数据 N<=100
对于40%的数据 N<=1000
对于60%的数据 N<=10000
对于100%的数据 N<=100000,M<=2*N
题解:
看到期望先想想期望DP(大佬@zcs0724说期望题基本都是期望DP。)
附:期望的概念:(摘自本蒟蒻的博客)
如果\(X\)是一个随机变量,它的取值分别是\(x_1,x_2\cdots x_n\),那么一个随机事件就可以表示为\(X=X_i\),假设它的概率是\(P(X=X_i)=p_i\),那么它的期望被称为\(E(X)\),有:
\[E(X)=\sum_{i=1}^{n}p_i\times x_i\]
通俗地理解,一个随机变量的期望就是这个随机变量的所有取值与其概率的乘积之和。
如果对概率论的其他概念还有知识盲区的,敬请翻看本蒟蒻的这篇博客:
那么按这个思路:
设\(E[x]\)为点\(x\)到终点\(n\)的路径期望总和。那么很显然地,答案为\(E[1]\),而初值\(E[n]\)为\(0\)。那么自然而然地,我们想到反向建图,从终点开始DP。从后往前开始逆推,最后得出\(E[1]\)的值。
根据逆推的过程和期望的定义,那么每一个节点的\(E[x]\)就应该是这个节点的子节点(这时还是正着的图)的\(E\)值加上这个子节点到\(x\)的边长除以这个节点的出度。
也就是:
\[E[x]=\sum_{y\in to}\{(E[y]+val[i])\times\frac{1}{to}\}\]
我们实现这个逆推可以使用深搜,但是深搜常数比较大,所以我们选择拓扑排序(效果是一样的)。
具体实现的细节是:
我们建图的时候不用建两遍,直接一遍建完反向边即可。但是,我们记录度的时候还是要按原来的记录,而且要记录两遍,一遍随拓扑排序的过程而改变,另一个作为计算期望值的参数一直保持不变。
这也是最容易出锅的点。
代码:
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=1e5+10;
int n,m;
int tot,head[maxn],to[maxn<<1],val[maxn<<1],nxt[maxn<<1];
int out_degree[maxn],degree[maxn];
double f[maxn];
queue<int> q;
void add(int x,int y,int z)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
val[tot]=z;
}
int main()
{
scanf("%d%d",&n,&m);
q.push(n);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(y,x,z);
out_degree[x]++;
degree[x]++;
}
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
f[y]+=(f[x]+val[i])/degree[y];
out_degree[y]--;
if(!out_degree[y])
q.push(y);
}
}
printf("%.2lf",f[1]);
return 0;
}