概率DP
例1. 走路1
蜗蜗的世界里有 \(n\) 个城市,城市之间通过 \(m\) 条单向高速公路连接,初始他在 1 号城市。蜗蜗想去 \(n\) 号城市游玩,假设现在他在 \(x\) 号城市,他会等概率地选择从 \(x\) 出发的高速公路中的一条走过去。如果没有任何从 \(x\) 号城里出发的高速公路,他就只能留在原地了。蜗蜗会一直走直到他无路可走。
请问蜗蜗有多大的概率能够走到 \(n\) 号城市。
输入格式
第一行两个整数 \(n,m\)。
接下来 \(m\) 行,每行两个整数 \(x,y(1\le x\lt y\le n)\),描述一条从 \(x\) 号城市到 \(y\) 号城市的高速公路。
数据保证没有任何两条高速公路的 \(x,y\) 是相同的。
注意,数据没有保证 1 号城市一定能走到 \(n\) 号城市。
输出格式
一行一个小数表示答案。相对误差或绝对误差在 \(10^{-6}\) 内即为正确。
样例输入
3 2
1 2
1 3
样例输出
0.5000000000
数据范围
对于 \(100\%\) 的数据,\(2\le n\le 100,1\le m\le 1000\)。
题解
- 假设计算的结果为 \(b\),标程答案为 \(a\),因为概率涉及到浮点数,因此计算结果通常会产生精度误差
- 什么是绝对误差:\(|b-a|\)
- 什么是相对误差:\(|b-a|/|a|\)
- 令 \(f[x]\) 表示能走到 \(x\) 号城市的概率,\(f[1]=1\),从 \(x\) 号城市出发到 \(y\) 号城市,\(f[y]+=f[x]/d[x]\),其中 \(d[x]\) 表示从 \(x\) 号城市出发的高速公路一共有 \(d[x]\) 条。
则 \(f[y]=\sum_{x\in pre(y)}{f[x]\over d[x]}\),\(pre(y)\) 表示所有连接到 \(y\) 号城市的高速公路的前趋点集合。 - 整体时间复杂度 \(O(n+m)\)。
#include <iostream>
#include <vector>
using namespace std;
const int N = 107;
vector<int> g[N];
double f[N];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
f[1] = 1;
for (int i = 1; i < n; ++i) {
for (int j : g[i])
f[j] += f[i] / g[i].size();
}
printf("%.10f\n", f[n]);
}
例2. 走路2
蜗蜗的世界里有 \(n\) 个城市,城市之间通过 \(m\) 条单向高速公路连接,初始他在 1 号城市。蜗蜗想去 \(n\) 号城市游玩,假设现在他在 \(x\) 号城市,他会等概率地选择从 \(x\) 出发的高速公路中的一条走过去。如果没有任何从 \(x\) 号城里出发的高速公路,他就只能留在原地了。蜗蜗会一直走直到他无路可走。
请问蜗蜗有多大的概率能够走到 \(n\) 号城市。
输入格式
第一行两个整数 \(n,m\)。
接下来 \(m\) 行,每行两个整数 \(x,y(1\le x\lt y\le n)\),描述一条从 \(x\) 号城市到 \(y\) 号城市的高速公路。
数据保证没有任何两条高速公路的 \(x,y\) 是相同的。
注意,数据没有保证 1 号城市一定能走到 \(n\) 号城市。
输出格式
一行一个整数(由于答案本身可能是分数,请输出答案 \(mod\ (10^9+7)\) 的值)。
样例输入
3 2
1 2
1 3
样例输出
500000004
数据范围
对于 \(100\%\) 的数据,\(2\le n\le 100,1\le m\le 1000\)。
题解
- 最简分数 \(a/b\ mod\ p\),即找到整数 \(c\),使得 \(bc\equiv a(mod\ p)\)
由于 \(p\) 是质数,根据费马小定理,\(b^{p-1}\equiv 1(mod\ p)\),也即 \(b^{p-2}\equiv b^{-1}(mod\ p)\),过程中所有 \(a/b\ mod\ p\) 都可以用 \(a\cdot b^{p-2}\ mod\ p\) 代替。 - \(f[y]=\sum_{x\in pre(y)}f[x]\ast d[x]^{p-2}\ mod\ p\)
- 整体时间复杂度:\(O(n+m\ast log\ p)\)
#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int N = 107, M = 1e9 + 7;
vector<int> g[N];
LL f[N];
LL qpow(LL a, int p) {
LL ans = 1;
for (; p; p>>=1, (a *= a)%=M)
if (p & 1) (ans *= a) %= M;
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
f[1] = 1;
for (int i = 1; i < n; ++i) {
LL r = qpow(g[i].size(), M-2);
for (int j : g[i]) {
f[j] += f[i] * r;
f[j] %= M;
}
}
printf("%lld\n", f[n]);
}
例3. 走路3
蜗蜗的世界里有 \(n\) 个城市,城市之间通过 \(m\) 条单向高速公路连接,初始他在 1 号城市。蜗蜗想去 \(n\) 号城市游玩,假设现在他在 \(x\) 号城市,他会等概率地选择从 \(x\) 出发的高速公路中的一条走过去。如果没有任何从 \(x\) 号城里出发的高速公路,他就只能留在原地了。蜗蜗会一直走直到他走到 \(n\) 号城市。
请问蜗蜗期望经过多少条高速公路能够走到 \(n\) 号城市。
输入格式
第一行两个整数 \(n,m\)。
接下来 \(m\) 行,每行两个整数 \(x,y(1\le x\lt y\le n)\),描述一条从 \(x\) 号城市到 \(y\) 号城市的高速公路。
数据保证没有任何两条高速公路的 \(x,y\) 是相同的。
注意,数据保证所有城市都能走到 \(n\) 号城市。
输出格式
一行一个整数(由于答案本身可能是分数,请输出答案 \(mod\ (10^9+7)\) 的值)。
样例输入
3 3
1 2
1 3
2 3
样例输出
500000005
数据范围
对于 \(100\%\) 的数据,\(2\le n\le 100,1\le m\le 1000\)。
题解
点击查看普通做法
令 \(X_i\) 表示从 1 号点出发,经过 \(i\) 条边走到 \(n\) 号点的事件,则 \(E(X)=\sum_{i=1}^n P(X_i)\cdot i\)
用 \(f[i][j]\) 表示从 1 号点出发,经过 \(j\) 条高速公路走到 \(i\) 号城市的概率,有:\(f[i][j]=\sum_{x\in pre(i)}{f[x][j-1]\over d[x]}\)
答案即为:\(\sum_{i=1}^{n-1}f[n][i]\cdot i\),时间复杂度为:\(O(nmlog\ M)\)
点击查看代码
#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int N = 107, M = 1e9 + 7;
vector<int> g[N];
LL f[N][N];
LL qpow(LL a, int p) {
LL ans = 1;
for (; p; p>>=1, (a *= a)%=M)
if (p & 1) (ans *= a) %= M;
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
f[1][0] = 1;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (!f[i][j]) continue;
LL r = qpow(g[i].size(), M-2);
for (int k : g[i]) {
f[k][j+1] += f[i][j] * r;
f[k][j+1] %= M;
}
}
}
LL ans = 0;
for (int i = 1; i < n; ++i)
(ans += f[n][i] * i) %= M;
printf("%lld\n", ans);
}
能不能更快呢?
点击查看更高效的做法
用 \(f[x]\) 表示从 \(X\) 号城市出发期望经过多少条高速公路能够走到 \(n\) 号城市,\(f[n]=0;\)
\(f[x]=\sum_{y\in nx(x)}{f[y]+1\over d[x]}=1+\sum_{y\in nx(x)}{f[y]\over d[x]}\),其中 \(P(y|x)=1/d[x],d[x]=|nx(x)|\)
时间复杂度:\(O(mlog\ M)\)
高效做法:概率从起点往终点求,期望从终点往起点求。
点击查看代码
#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int N = 107, M = 1e9 + 7;
vector<int> g[N];
LL f[N];
LL qpow(LL a, int p) {
LL ans = 1;
for (; p; p>>=1, (a *= a)%=M)
if (p & 1) (ans *= a) %= M;
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
for (int i = n-1; i; --i) {
LL r = qpow(g[i].size(), M-2);
f[i] = 1;
for (int j : g[i])
(f[i] += f[j] * r) %= M;
}
printf("%lld\n", f[1]);
}
例4. 瓜子
小 L 买了 \(n\) 粒瓜子。
小 L 每次都会从这堆瓜子中挑出一粒,他每次吃完一粒瓜子后,就会得到两瓣瓜子壳,他会把瓜子壳丢进瓜子堆里去。
如果他拿到的是自己之前吃瓜子留下的一瓣瓜子壳,他就会把拿到的瓜子壳丢掉,否则就吃掉拿到的瓜子并且把瓜子壳丢进去。
现在设每次小 L 拿到每一粒瓜子或者瓜子壳的概率是均等的,问:小 L 期望多少次能够把瓜子拿完。
输入格式
一行一个正整数 \(n\)。
输出格式
一行一个整数表示结果对于 \(998244353\) 取模的结果。如果答案可以被表示成最简分数 \(p/q\),那么输出一个数字 \(r\),满足 \(qr\equiv p(mod\ 998244353)\)。
样例输入
2
样例输出
3
样例解释
\(n=2\) 的时候,小 L 第一次拿到的肯定是瓜子,然后现在瓜子堆里面有 1 粒瓜子,2 个瓜子壳。接下来他有 \(1\over 3\) 的概率拿到瓜子,有 \({2\over 3}\times {1\over 3}\) 的概率第一次拿到瓜子壳,第二次拿到瓜子。还有 \({2\over 3}\times {1\over 2}={1\over 3}\) 的概率再拿两次都拿到瓜子壳,最后拿到瓜子。
所以期望的次数:\(2\times {1\over 3}+3\times {1\over 3}+4\times {1\over 3}=3\)
数据规模
对于 \(100\%\) 的数据满足 \(n\le 2\times 10^3\)。
题解
点击查看题解
用 \(f[i][j]\) 表示还剩下 \(i\) 粒瓜子和 \(j\) 瓣瓜子壳时,拿完瓜子的数学期望次数,\(f[0][j] = 0\)。
\(f[i][j] = {i\over i+j}f[i-1][j+2]+{j\over i+j}f[i][j-1] + 1\)
答案为:\(f[n][0]\)
时间复杂度:\(O(n^2log\ M)\)
点击查看代码
#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int N = 2007, M = 998244353;
vector<int> g[N];
LL f[N][N<<1], qp[N<<1];
LL qpow(LL a, int p) {
LL ans = 1;
for (; p; p>>=1, (a *= a)%=M)
if (p & 1) (ans *= a) %= M;
return ans;
}
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n+n; ++i)
qp[i] = qpow(i, M-2);
for (int i = 1; i <= n; ++i)
for (int j = 0; i+i+j <= n+n; ++j) {
LL dn = qpow(i + j, M - 2);
f[i][j] = i * dn * f[i-1][j+2] + 1;
if (j) f[i][j] += j * dn * f[i][j-1];
f[i][j] %= M;
}
printf("%lld\n", f[n][0]);
}
例5. 走路4
蜗蜗的世界里有 \(n\) 个城市,城市之间通过 \(m\) 条单向高速公路连接,初始他在 1 号城市。蜗蜗想去 \(n\) 号城市游玩,假设现在他在 \(x\) 号城市,他会等概率地选择从 \(x\) 出发的高速公路中的一条走过去。如果没有任何从 \(x\) 号城里出发的高速公路,他就只能留在原地了。蜗蜗会一直走直到他走到 \(n\) 号城市。
请问蜗蜗期望经过多少条高速公路能够走到 \(n\) 号城市。
输入格式
第一行两个整数 \(n,m\)。
接下来 \(m\) 行,每行两个整数 \(x,y(1\le x, y\le n,x\neq y)\),描述一条从 \(x\) 号城市到 \(y\) 号城市的高速公路。
数据保证没有任何两条高速公路的 \(x,y\) 是相同的。
注意,数据保证所有城市都能走到 \(n\) 号城市。
输出格式
一行一个整数(由于答案本身可能是分数,请输出答案 \(mod\ (10^9+7)\) 的值)。
样例输入
3 3
1 2
2 1
2 3
样例输出
4
数据范围
对于 \(100\%\) 的数据,\(2\le n\le 100,1\le m\le 1000\)。
题解
点击查看题解
用 \(f[x]\) 表示从 \(x\) 号城市出发,期望经过多少条高速公路能够到达 \(n\) 号城市,\(f[n] = 0;\)
再看之前的思路,\(f[x]=\sum_{y\in nx(x)}{f[y]+1\over d[x]}=1+\sum_{y\in nx(x)}{f[y]\over d[x]}\),其中 \(P(y|x)=1/d[x],d[x]=|nx(x)|\)
这个做法就不适用了!!!
因为本题的边不再是从编号较小的点指向编号较大的点,从而有可能产生回路,使得DP过程不在满足一个DAG结构
带回路的递推结构,请考虑高斯消元!
时间复杂度:\(O(n^3log\ M)\)
点击查看代码
#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int N = 107, M = 1e9 + 7;
vector<int> g[N];
LL f[N][N], v[N], a[N];
int n, m;
LL qpow(LL a, int p) {
LL ans = 1;
for (; p; p>>=1, (a *= a)%=M)
if (p & 1) (ans *= a) %= M;
return ans;
}
void gauss() {
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j)
// 如果是小数的时候,需要找到这个位置值最大的替换,以得到最好的精度,f[j][i]>f[i][i]
if (f[j][i]) {
for (int k = i; k <= n; ++k)
swap(f[i][k], f[j][k]);
swap(v[i], v[j]);
break;
}
for (int j = i+1; j <= n; ++j)
if (f[j][i]) {
LL delta = f[j][i] * qpow(f[i][i], M-2) % M;
for (int k = i; k <= n; ++k) {
f[j][k] -= f[i][k] * delta % M;
if (f[j][k] < 0) f[j][k] += M;
}
v[j] -= v[i] * delta % M;
if (v[j] < 0) v[j] += M;
}
}
for (int i = n; i; --i) {
for (int j = i+1; j <= n; ++j) {
v[i] -= f[i][j] * a[j] % M;
if (v[i] < 0)
v[i] += M;
}
a[i] = v[i] * qpow(f[i][i], M-2) % M;
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
for (int i = 1; i < n; ++i) {
f[i][i] = 1, v[i] = 1;
LL r = qpow(g[i].size(), M - 2);
for (int j : g[i])
f[i][j] = M - r;
}
f[n][n] = 1;
gauss();
printf("%lld\n", a[1]);
}
例6. Passage
Bill 是一个百万富翁,但不幸被困在了一座城堡里。
城堡有 \(n\) 条出口,对于每条出口 \(i(1\le i\le n)\):
-
\(P_i(0\le P_i\le 1)\) 表示选择这条出口逃脱的成功概率。
-
\(Q_i(0\le Q_i\le 1-P_i)\) 表示这条出口上有一组守卫的概率。
-
如果 Bill 选择这条出口,他需要支付一百万美元并返回城堡。
-
否则,他将被杀害。
-
-
这条出口成为死胡同的概率为 \(1-P_i-Q_i\)。在这种情况下,Bill 必须返回城堡。
-
每次返回城堡后,他可以选择另一条出口。
我们已经知道 Bill 有 \(M\) 百万美元。请帮助 Bill 找出他可以选择的最佳策略逃脱的概率。
输入格式
第一行包含一个整数 \(T(T\le 100)\),表示测试用例的数量。
对于每个测试用例:
第一行包含两个整数 \(n(1\le n\le 1000)\) 和 \(M(0\le M\le 10)\)。
接下来的 \(n\) 行中,每行包含两个浮点数 \(P_i\) 和 \(Q_i\)。
输出格式
对于每个测试用例,输出案例编号和答案。
答案应保留小数点后五位。
样例输入
3
1 10
0.5 0
2 0
0.3 0.4
0.4 0.5
3 0
0.333 0.234
0.353 0.453
0.342 0.532
样例输出
Case 1: 0.50000
Case 2: 0.43000
Case 3: 0.51458
题解
点击查看题解
这个问题实际上求选择哪些路已经按照某种顺序去试,使得逃出个概率最大。
先不考虑钱这个维度。考虑相邻的两条路,确定先后顺序。
\(f(a,b)\) 表示先选 \(a\) 这条路再选 \(b\) 这条路。
假设 \(f(a,b)\) 优于 \(f(b,a)\),即 \(f(a,b)>f(b,a)\) 有
最后的表达式说明哪条应当排在前面只跟 \({P_i\over Q_i}\) 这个值有关。
因此要按照这个顺序去决策选择哪些道路。
\(dp[i][j]\) 表示从第 \(i\) 条路开始选择,还有 \(j\) 元钱逃出的最大概率。最终答案为 \(dp[1][m]\)。
\(dp[i+1][j]\) 表示不选第 \(i\) 条路,剩下的表示能逃出的概率由 \(3\) 部分组成。
1:\(P_i\) 表示这次就逃出去的概率。
2:\(dp[i+1][j]\ast(1-P_i-Q_i)\) 表示本次是死胡同,由下一条路的状态决定。
3:\(dp[i+1][j-1]\ast Q_i\) 表示本次需要花钱,由下一条路的状态决定。

浙公网安备 33010602011771号