洛谷P4316 绿豆蛙的归宿(期望dp)
原题链接:https://www.luogu.com.cn/problem/P4316
这题是经典的概率dp题,通常看到的题解都是逆推的做法,实际上理解了题目的含义后发现逆推其实是正推的一种特殊情况而已
正推做法:
定义dp[i]
表示从1~i
的路径长度的期望,那么dp[1] = 0
,答案就是dp[n]
状态转移公式:
// u -> v
dp[v] = (dp[u] + p[u] * w[u, v]) / out[u]; // 路径长度期望转移
p[v] += p[u] / out[u]; // 概率转移
上式中,dp[v]
表示1~v
的路径长度的期望,dp[u]
表示1~u
的路径长度的期望,p[v]
表示从1到v的概率,w[u, v]
表示u和v相连的边的权值,out[u]
表示u的出度
状态转移时要乘概率的原因就类似于乘法原理,概率转移公式中用到了加法原理~~
由于是有向无环图,所以用拓扑排序做即可
贴一下代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a;i < b;i++)
#define per(i,a,b) for(int i = b - 1;i >= a;i--)
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> VI;
const int INF = 0x3f3f3f3f, N = 100010;
double dp[N], p[N];
int in[N], out[N], n, m;
vector<PII> G[N];
int main() {
scanf("%d%d", &n, &m);
while(m--) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G[u].push_back({v, w});
in[v]++;
out[u]++;
}
// 拓扑排序
queue<int> q;
p[1] = 1.0; dp[1] = 0.0; // 初始状态
q.push(1); // 将起点入队
while(q.size()) {
int u = q.front(); q.pop();
for(auto x : G[u]) {
int v = x.fi, w = x.se;
dp[v] += (dp[u] + p[u] * w) / out[u];
p[v] += p[u] / out[u];
if(--in[v] == 0)
q.push(v);
}
}
printf("%.2lf\n", dp[n]);
return 0;
}
逆推做法:
定义dp[i]
表示从i~n
的路径长度的期望,那么dp[n] = 0
,答案就是dp[1]
由于是逆推,所以我们反向建图,从n倒退回1
状态转移公式:
// u -> v
dp[v] = (dp[u] + p[u] * w[u, v]) / deg[v]; // 路径长度期望转移
上式中,dp[v]
表示v~n
的路径长度的期望,dp[u]
表示u~n
的路径长度的期望,p[u]
表示从v到n的概率(显然每个点一定能到n,所以p[u]恒为1),w[u, v]
表示u和v相连的边的权值,deg[v]表示反图中v的入度,也即原图中v的出度
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a;i < b;i++)
#define per(i,a,b) for(int i = b - 1;i >= a;i--)
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> VI;
const int INF = 0x3f3f3f3f, N = 100010;
double dp[N];
int in[N], deg[N], n, m;
vector<PII> G[N];
int main() {
scanf("%d%d", &n, &m);
while(m--) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G[v].push_back({u, w});
in[u]++;
deg[u]++;
}
// 拓扑排序
queue<int> q;
q.push(n);
while(q.size()) {
int u = q.front(); q.pop();
for(auto x : G[u]) {
int v = x.fi, w = x.se;
dp[v] += (dp[u] + 1.0 * w) / deg[v];
if(--in[v] == 0) q.push(v);
}
}
printf("%.2lf\n", dp[1]);
return 0;
}
你只管出发,旅途自有风景~~