Probability DP
概率 DP 用于解决概率问题与期望问题,建议先对 概率 & 期望 的内容有一定了解。一般情况下,解决概率问题需要顺序循环,而解决期望问题使用逆序循环,如果定义的状态转移方程存在后效性问题,还需要用到 高斯消元 来优化。概率 DP 也会结合其他知识进行考察,例如 状态压缩,树上进行 DP 转移等。
DP 求概率¶
这类题目采用顺推,也就是从初始状态推向结果。同一般的 DP 类似的,难点依然是对状态转移方程的刻画,只是这类题目经过了概率论知识的包装。
例题 Codeforces 148 D Bag of mice
题目大意:袋子里有 w 只白鼠和 b 只黑鼠,公主和龙轮流从袋子里抓老鼠。谁先抓到白色老鼠谁就赢,如果袋子里没有老鼠了并且没有谁抓到白色老鼠,那么算龙赢。公主每次抓一只老鼠,龙每次抓完一只老鼠之后会有一只老鼠跑出来。每次抓的老鼠和跑出来的老鼠都是随机的。公主先抓。问公主赢的概率。
设
- 公主抓到一只白鼠,公主赢了。概率为
\frac{i}{i+j} - 公主抓到一只黑鼠,龙抓到一只白鼠,龙赢了。概率为
\frac{j}{i+j}\cdot \frac{i}{i+j-1} - 公主抓到一只黑鼠,龙抓到一只黑鼠,跑出来一只黑鼠,转移到
f_{i,j-3} \frac{j}{i+j}\cdot\frac{j-1}{i+j-1}\cdot\frac{j-2}{i+j-2} - 公主抓到一只黑鼠,龙抓到一只黑鼠,跑出来一只白鼠,转移到
f_{i-1,j-2} \frac{j}{i+j}\cdot\frac{j-1}{i+j-1}\cdot\frac{i}{i+j-2}
考虑公主赢的概率,第二种情况不参与计算。并且要保证后两种情况合法,所以还要判断
参考实现
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int w, b;
double dp[1010][1010];
int main() {
scanf("%d %d", &w, &b);
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= w; i++) dp[i][0] = 1;
for (int i = 1; i <= b; i++) dp[0][i] = 0;
for (int i = 1; i <= w; i++) {
for (int j = 1; j <= b; j++) {
dp[i][j] += (double)i / (i + j);
if (j >= 3) {
dp[i][j] += (double)j / (i + j) * (j - 1) / (i + j - 1) * (j - 2) /
(i + j - 2) * dp[i][j - 3];
}
if (i >= 1 && j >= 2) {
dp[i][j] += (double)j / (i + j) * (j - 1) / (i + j - 1) * i /
(i + j - 2) * dp[i - 1][j - 2];
}
}
}
printf("%.9lf\n", dp[w][b]);
return 0;
}
习题¶
DP 求期望¶
例题 POJ2096 Collecting Bugs
题目大意:一个软件有 s 个子系统,会产生 n 种 bug。某人一天发现一个 bug,这个 bug 属于某种 bug 分类,也属于某个子系统。每个 bug 属于某个子系统的概率是
令
考虑
f_{i,j} i j p_1=\frac{i}{n}\cdot\frac{j}{s} f_{i,j+1} i p_2=\frac{i}{n}\cdot(1-\frac{j}{s}) f_{i+1,j} j p_3=(1-\frac{i}{n})\cdot\frac{j}{s} f_{i+1,j+1} p_4=(1-\frac{i}{n})\cdot(1-\frac{j}{s})
再根据期望的线性性质,就可以得到状态转移方程:
参考实现
#include <cstdio>
using namespace std;
int n, s;
double dp[1010][1010];
int main() {
scanf("%d %d", &n, &s);
dp[n][s] = 0;
for (int i = n; i >= 0; i--) {
for (int j = s; j >= 0; j--) {
if (i == n && s == j) continue;
dp[i][j] = (dp[i][j + 1] * i * (s - j) + dp[i + 1][j] * (n - i) * j +
dp[i + 1][j + 1] * (n - i) * (s - j) + n * s) /
(n * s - i * j);
}
}
printf("%.4lf\n", dp[0][0]);
return 0;
}
例题 「NOIP2016」换教室
题目大意:牛牛要上
对于这个无向连通图,先用 Floyd 求出最短路,为后续的状态转移带来便利。以移动一步为一个阶段(从第
考虑
- 如果这一阶段不换,即
f_{i,j,0} f_{i-1,j,0}+w_{c_{i-1},c_{i}} f_{i-1,j,1}+w_{d_{i-1},c_{i}}\cdot p_{i-1}+w_{c_{i-1},c_{i}}\cdot (1-p_{i-1})
- 如果这一阶段交换,即
f_{i,j,1} (1-p_i) p_i
参考实现
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2010;
int n, m, v, e;
int f[maxn][maxn], c[maxn], d[maxn];
double dp[maxn][maxn][2], p[maxn];
int main() {
scanf("%d %d %d %d", &n, &m, &v, &e);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
for (int i = 1; i <= n; i++) scanf("%d", &d[i]);
for (int i = 1; i <= n; i++) scanf("%lf", &p[i]);
for (int i = 1; i <= v; i++)
for (int j = 1; j < i; j++) f[i][j] = f[j][i] = 1e9;
int u, V, w;
for (int i = 1; i <= e; i++) {
scanf("%d %d %d", &u, &V, &w);
f[u][V] = f[V][u] = min(w, f[u][V]);
}
for (int k = 1; k <= v; k++)
for (int i = 1; i <= v; i++)
for (int j = 1; j < i; j++)
if (f[i][k] + f[k][j] < f[i][j]) f[i][j] = f[j][i] = f[i][k] + f[k][j];
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++) dp[i][j][0] = dp[i][j][1] = 1e9;
dp[1][0][0] = dp[1][1][1] = 0;
for (int i = 2; i <= n; i++)
for (int j = 0; j <= min(i, m); j++) {
dp[i][j][0] = min(dp[i - 1][j][0] + f[c[i - 1]][c[i]],
dp[i - 1][j][1] + f[c[i - 1]][c[i]] * (1 - p[i - 1]) +
f[d[i - 1]][c[i]] * p[i - 1]);
if (j != 0) {
dp[i][j][1] = min(dp[i - 1][j - 1][0] + f[c[i - 1]][d[i]] * p[i] +
f[c[i - 1]][c[i]] * (1 - p[i]),
dp[i - 1][j - 1][1] +
f[c[i - 1]][c[i]] * (1 - p[i - 1]) * (1 - p[i]) +
f[c[i - 1]][d[i]] * (1 - p[i - 1]) * p[i] +
f[d[i - 1]][c[i]] * (1 - p[i]) * p[i - 1] +
f[d[i - 1]][d[i]] * p[i - 1] * p[i]);
}
}
double ans = 1e9;
for (int i = 0; i <= m; i++) ans = min(dp[n][i][0], min(dp[n][i][1], ans));
printf("%.2lf", ans);
return 0;
}
比较这两个问题可以发现,DP 求期望题目在对具体是求一个值或是最优化问题上会对方程得到转移方式有一些影响,但无论是 DP 求概率还是 DP 求期望,总是离不开概率知识和列出、化简计算公式的步骤,在写状态转移方程时需要思考的细节也类似。
习题¶
有后效性 DP¶
CodeForces 24 D Broken robot
题目大意:给出一个
在
f_{i,1}=\frac{1}{3}\cdot(f_{i+1,1}+f_{i,2}+f_{i,1})+1 f_{i,j}=\frac{1}{4}\cdot(f_{i,j}+f_{i,j-1}+f_{i,j+1}+f_{i+1,j})+1 f_{i,m}=\frac{1}{3}\cdot(f_{i,m}+f_{i,m-1}+f_{i+1,m})+1
在行之间由于只能向下移动,是满足无后效性的。在列之间可以左右移动,在移动过程中可能产生环,不满足无后效性。 将方程变换后可以得到:
2f_{i,1}-f_{i,2}=3+f_{i+1,1} 3f_{i,j}-f_{i,j-1}-f_{i,j+1}=4+f_{i+1,j} 2f_{i,m}-f_{i,m-1}=3+f_{i+1,m}
由于是逆序的递推,所以每一个
参考实现
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
double a[maxn][maxn], f[maxn];
int n, m;
void solve(int x) {
memset(a, 0, sizeof a);
for (int i = 1; i <= m; i++) {
if (i == 1) {
a[i][i] = 2;
a[i][i + 1] = -1;
a[i][m + 1] = 3 + f[i];
continue;
} else if (i == m) {
a[i][i] = 2;
a[i][i - 1] = -1;
a[i][m + 1] = 3 + f[i];
continue;
}
a[i][i] = 3;
a[i][i + 1] = -1;
a[i][i - 1] = -1;
a[i][m + 1] = 4 + f[i];
}
for (int i = 1; i < m; i++) {
double p = a[i + 1][i] / a[i][i];
a[i + 1][i] = 0;
a[i + 1][i + 1] -= a[i][i + 1] * p;
a[i + 1][m + 1] -= a[i][m + 1] * p;
}
f[m] = a[m][m + 1] / a[m][m];
for (int i = m - 1; i >= 1; i--)
f[i] = (a[i][m + 1] - f[i + 1] * a[i][i + 1]) / a[i][i];
}
int main() {
scanf("%d %d", &n, &m);
int st, ed;
scanf("%d %d", &st, &ed);
if (m == 1) {
printf("%.10f\n", 2.0 * (n - st));
return 0;
}
for (int i = n - 1; i >= st; i--) {
solve(i);
}
printf("%.10f\n", f[ed]);
return 0;
}
习题¶
参考文献¶
buildLast update and/or translate time of this article,Check the history
editFound smelly bugs? Translation outdated? Wanna contribute with us? Edit this Page on Github
peopleContributor of this article OI-wiki
translateTranslator of this article Visit the original article!
copyrightThe article is available under CC BY-SA 4.0 & SATA ; additional terms may apply.