Introduction
Probability DP is used for probability and expected-value problems. It is recommended to first become familiar with Probability and expectation. In general, probability problems are solved by iterating forwards, while expected-value problems are solved by iterating backwards. If the state-transition equations have dependencies on states that are not yet known, Gaussian elimination is also needed. Probability DP is often combined with other techniques, such as state compression and DP transitions on trees.
Probability DP
These problems use forward transitions, moving from the initial state toward the result. As in ordinary DP, the main difficulty is still describing the state transition correctly; probability theory is simply used to formulate the transitions.
Examples
Codeforces 148D Bag of mice
A bag contains white mice and black mice. A princess and a dragon take turns drawing mice from the bag. Whoever draws a white mouse first wins. If the bag becomes empty without either player drawing a white mouse, the dragon wins. The princess draws one mouse on each turn; after the dragon draws one mouse, another mouse escapes from the bag. Every drawn or escaping mouse is chosen uniformly at random. The princess moves first. What is the probability that the princess wins?
Solution
Let be the probability that the princess wins when it is her turn and the bag contains white mice and black mice. Initialize the boundary conditions: , because with no white mice the dragon wins, and , because the next mouse drawn is white. Consider the transition for :
- The princess draws a white mouse and wins, with probability .
- The princess draws a black mouse and the dragon draws a white mouse, so the dragon wins, with probability .
- The princess draws a black mouse, the dragon draws a black mouse, and another black mouse escapes; transition to with probability .
- The princess draws a black mouse, the dragon draws a black mouse, and a white mouse escapes; transition to with probability .
The second case does not contribute to the princess's winning probability. The last two cases must also be legal: check the sizes of ; the third requires at least 3 black mice, while the fourth requires at least 1 white mouse and 2 black mice.
Reference code
#include <cstring>
#include <iomanip>
#include <iostream>
using namespace std;
using ll = long long;
int w, b;
double dp[1010][1010];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> w >> b;
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= w; i++) dp[i][0] = 1; // Initialize the boundary
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++) { // Probability transitions from the statement
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];
}
}
}
cout << fixed << setprecision(9) << dp[w][b] << '\n';
return 0;
}
Exercises
Expected-value DP
Examples
POJ2096 Collecting Bugs
A software system has subsystems and produces kinds of bugs. Each day, a person discovers one bug. The bug belongs to one bug category and one subsystem. Each subsystem is equally likely, with probability , and each bug category is equally likely, with probability . Find the expected number of days needed to discover all kinds of bugs and find at least one bug in every one of the subsystems.
Solution
Let be the expected number of days needed to reach the target after bug categories and bugs in subsystems have been found. The target state is finding all categories and all subsystems, so . Starting from the target state, compute the transitions backwards; the answer is .
Consider the transitions from :
- The new bug belongs to one of the already discovered categories and one of the already discovered subsystems: , with probability .
- It belongs to one of the discovered categories but to an undiscovered subsystem: , with probability .
- It belongs to an undiscovered category and one of the discovered subsystems: , with probability .
- It belongs to an undiscovered category and an undiscovered subsystem: , with probability .
By linearity of expectation, the transition equation is
Reference code
#include <iomanip>
#include <iostream>
using namespace std;
int n, s;
double dp[1010][1010];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> 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); // Probability transition
}
}
cout << fixed << setprecision(4) << dp[0][0] << '\n';
return 0;
}
「NOIP2016」Changing Classrooms
Niuniu attends classes in time slots. In slot , the class is held in classroom ; he may apply to move it to classroom , and the application succeeds with probability . He may apply to change at most classes. After the class in slot , he walks from the current classroom to the classroom for slot . Given a graph with classrooms and roads, where walking consumes energy, choose the classes to change so that the expected total walking cost is minimized; equivalently, find the minimum expected path length.
Solution
For this undirected connected graph, first use Floyd–Warshall to compute all-pairs shortest paths. Treat the movement from slot to slot as one stage. At each stage, the destination is with probability (if the change is applied) and with probability . At most changes may be applied; only m of the destinations may be selected. The goal is the minimum expected total distance over the stages.
Let be the minimum expected distance after reaching the classroom for slot , having used classroom-change opportunities including this slot, and choosing not to change (0) or to change (1) in this slot. The answer is . The boundary conditions are .
Consider the transitions for :
- If this stage does not use a change, may come from the previous non-change state, giving , or from the previous change state. Using conditional probability and the law of total probability, the latter is . Thus,
- If this stage uses a change, giving , it may likewise come from either the previous non-change state or the previous change state. Multiply by when the previous application fails and by when it succeeds, then enumerate all possible cases. The resulting transitions follow directly from the preceding example.
Reference code
#include <algorithm>
#include <iomanip>
#include <iostream>
using namespace std;
constexpr 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() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n >> m >> v >> e;
for (int i = 1; i <= n; i++) cin >> c[i];
for (int i = 1; i <= n; i++) cin >> d[i];
for (int i = 1; i <= n; i++) cin >> 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++) {
cin >> 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++) // Transfer the preceding DP as described above
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++) // Transition with future dependence
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));
cout << fixed << setprecision(2) << ans;
return 0;
}
Comparing these two types of problems shows that expected-value DP may produce different transitions depending on whether the goal is to compute a value or optimize one. Nevertheless, both probability DP and expected-value DP rely on probability theory, writing down and simplifying the formulas, and carefully identifying the details of each state transition.
Exercises
DP with future dependence
Examples
CodeForces 24D Broken robot
Consider an grid. A robot starts at row , column . At every step it chooses, with equal probability, to stay in place, move one cell left, move one cell right, or move one cell down. At a boundary it cannot move outside the grid. Find the expected number of steps until it reaches the last row.
Solution
When , the robot stays in place with probability and moves down one cell with probability , so the answer is . Let be the expected number of steps for a robot starting at row i, column j to reach row . The terminal states are . Since the robot chooses to stay, move left, move right, or move down with equal probability, the transitions for are:
Movement between rows only goes down, so it has no future dependence. Movement between columns can go left or right and may create cycles, so it does have future dependence. Rearranging the equations gives:
Because the recurrence is evaluated backwards, every is already known. There are columns, so the right-hand side is an -row column vector, while the left-hand side is an -row, -column matrix. The augmented matrix therefore has rows and columns; applying Gaussian elimination solves for the answer.
Reference code
#include <cstdio>
#include <cstring>
using namespace std;
constexpr int MAXN = 1e3 + 10;
double a[MAXN][MAXN], f[MAXN];
int n, m;
void solve(int x) {
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;
}