【题目描述】
给出一个有向无环的连通图,起点为 $1$ 终点为 $N$ ,边有边权。
从起点走向终点,到达每一个顶点时,如果有 $K$ 条离开该点的道路,可以选择任意一条道路离开该点,并且走向每条路的概率为 $\frac 1 K$ 。
求从起点 $1$ 到终点 $N$ 的路径长度的期望。
【题目链接】
BZOJ 3036 绿豆蛙的归宿
【解题思路】
期望 DP 入门题。
方法一:
直接 DP,设 $f[i]$ 表示从 $i$ 到终点的路径长度的期望。
我们可以根据全期望公式来转移(其中 $d_i$ 表示点 $i$ 的出度):
$$ f[i] = \sum_{(i, j) \in E} \frac 1 {d_i} \times (f[j] + w(i, j)) $$
即考虑 $i$ 的所有出边,沿任一条边走过去的概率为 $\frac 1 d_i$,而其期望长度为 $f[j] + w(i, j)$。
由于图是 DAG
,所以这个 DP 可以递推,按照拓扑序的逆序计算即可。
方法二:
根据期望的线性性质,路径长度的期望等于每条边的期望贡献之和,而每条边的期望贡献即为经过该边的期望次数乘上边权。
经过某边的期望次数不好计算,我们考虑计算经过某个点的期望次数,然后用其计算经过某边的期望次数。
设点 $i$ 的期望经过次数为 $f_i$ ,则对于边 $(u, v)$,经过其的期望次数为:
$$ f_u \times \frac 1 d_u $$
接下来我们考虑如何计算 $f_i$,考虑一个点的所有入边:
$$ f_i = \sum_{(j, i) \in E} f_j \times \frac 1 d_j $$
这是比较显然的。
考虑每个点可以转移到那儿也是可以的:
$$ f_j \leftarrow f_i \times \frac 1 d_i, (i, j) \in E $$
这样可以一边 DP 一边计算贡献,比较方便。
在实践中,方法二的应用更多,尽管在本题中还看不出其优势。
【AC代码】
方法一
#include <iostream>
#include <iomanip>
const int MAXN = 100000;
int n, m;
struct Node{
struct Edge *edges;
int inDeg, otDeg;
double exLen;
Node() : edges(NULL), exLen(0), inDeg(0), otDeg(0) {}
} nodes[MAXN];
struct Edge{
Node *to;
Edge *next;
int w;
Edge(Node *fr, Node *to, int w) : to(to), next(fr->edges), w(w) {}
};
inline void addEdge(Node *u, Node *v, int w){
u->edges = new Edge(u, v, w);
u->otDeg++, v->inDeg++;
}
Node* Q[MAXN];
inline void topo(Node *s){
int l = 0, r = 0;
Q[r++] = s;
while(l != r){
Node *v = Q[l++];
for(Edge *e = v->edges; e; e = e->next){
if(!--e->to->inDeg) Q[r++] = e->to;
}
}
}
inline double solve(){
topo(nodes);
(nodes + n - 1)->exLen = 0;
for(int i = n - 2; i >= 0; i--){
Node *v = nodes + i;
v->exLen = 0;
for(Edge *e = v->edges; e; e = e->next){
v->exLen += (e->to->exLen + e->w) / v->otDeg;
}
}
return nodes->exLen;
}
int main(){
std::cin >> n >> m;
for(int i = 0; i < m; i++){
int u, v, w;
std::cin >> u >> v >> w, u--, v--;
addEdge(nodes + u, nodes + v, w);
}
std::cout.setf(std::ios::fixed);
std::cout << std::setprecision(2) << solve() << std::endl;
return 0;
}
方法二
#include <iostream>
#include <iomanip>
const int MAXN = 100000;
int n, m;
struct Node{
struct Edge *edges;
int inDeg, otDeg;
double ex;
Node() : edges(NULL), ex(0), inDeg(0), otDeg(0) {}
} nodes[MAXN];
struct Edge{
Node *to;
Edge *next;
int w;
Edge(Node *fr, Node *to, int w) : to(to), next(fr->edges), w(w) {}
};
inline void addEdge(Node *u, Node *v, int w){
u->edges = new Edge(u, v, w);
u->otDeg++, v->inDeg++;
}
Node* Q[MAXN];
inline void topo(Node *s){
int l = 0, r = 0;
Q[r++] = s;
while(l != r){
Node *v = Q[l++];
for(Edge *e = v->edges; e; e = e->next){
if(!--e->to->inDeg) Q[r++] = e->to;
}
}
}
inline double solve(){
topo(nodes);
double ans = 0;
nodes->ex = 1;
for(int i = 0; i < n; i++){
Node *v = Q[i];
for(Edge *e = v->edges; e; e = e->next){
e->to->ex += v->ex / v->otDeg;
ans += v->ex * e->w / v->otDeg;
}
}
return ans;
}
int main(){
std::cin >> n >> m;
for(int i = 0; i < m; i++){
int u, v, w;
std::cin >> u >> v >> w, u--, v--;
addEdge(nodes + u, nodes + v, w);
}
std::cout.setf(std::ios::fixed);
std::cout << std::setprecision(2) << solve() << std::endl;
return 0;
}
就是这样咯~