Probability DP

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 ww white mice and bb 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 fi,jf_{i,j} be the probability that the princess wins when it is her turn and the bag contains ii white mice and jj black mice. Initialize the boundary conditions: f0,j=0f_{0,j}=0, because with no white mice the dragon wins, and fi,0=1f_{i,0}=1, because the next mouse drawn is white. Consider the transition for fi,jf_{i,j}:

  • The princess draws a white mouse and wins, with probability ii+j\dfrac{i}{i+j}.
  • The princess draws a black mouse and the dragon draws a white mouse, so the dragon wins, with probability ji+j⋅ii+j−1\dfrac{j}{i+j}\cdot\dfrac{i}{i+j-1}.
  • The princess draws a black mouse, the dragon draws a black mouse, and another black mouse escapes; transition to fi,j−3f_{i,j-3} with probability ji+j⋅j−1i+j−1⋅j−2i+j−2\dfrac{j}{i+j}\cdot\dfrac{j-1}{i+j-1}\cdot\dfrac{j-2}{i+j-2}.
  • The princess draws a black mouse, the dragon draws a black mouse, and a white mouse escapes; transition to fi−1,j−2f_{i-1,j-2} with probability ji+j⋅j−1i+j−1⋅ii+j−2\dfrac{j}{i+j}\cdot\dfrac{j-1}{i+j-1}\cdot\dfrac{i}{i+j-2}.

The second case does not contribute to the princess's winning probability. The last two cases must also be legal: check the sizes of i,ji,j; 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 ss subsystems and produces nn 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 1s\dfrac{1}{s}, and each bug category is equally likely, with probability 1n\dfrac{1}{n}. Find the expected number of days needed to discover all nn kinds of bugs and find at least one bug in every one of the ss subsystems.

Solution

Let fi,jf_{i,j} be the expected number of days needed to reach the target after ii bug categories and bugs in jj subsystems have been found. The target state is finding all nn categories and all ss subsystems, so fn,s=0f_{n,s}=0. Starting from the target state, compute the transitions backwards; the answer is f0,0f_{0,0}.

Consider the transitions from fi,jf_{i,j}:

  • The new bug belongs to one of the ii already discovered categories and one of the jj already discovered subsystems: fi,jf_{i,j}, with probability p1=in⋅jsp_1=\dfrac{i}{n}\cdot\dfrac{j}{s}.
  • It belongs to one of the ii discovered categories but to an undiscovered subsystem: fi,j+1f_{i,j+1}, with probability p2=in⋅(1−js)p_2=\dfrac{i}{n}\cdot(1-\dfrac{j}{s}).
  • It belongs to an undiscovered category and one of the jj discovered subsystems: fi+1,jf_{i+1,j}, with probability p3=(1−in)⋅jsp_3=(1-\dfrac{i}{n})\cdot\dfrac{j}{s}.
  • It belongs to an undiscovered category and an undiscovered subsystem: fi+1,j+1f_{i+1,j+1}, with probability p4=(1−in)⋅(1−js)p_4=(1-\dfrac{i}{n})\cdot(1-\dfrac{j}{s}).

By linearity of expectation, the transition equation is

fi,j=p1⋅fi,j+p2⋅fi,j+1+p3⋅fi+1,j+p4⋅fi+1,j+1+1=p2⋅fi,j+1+p3⋅fi+1,j+p4⋅fi+1,j+1+11−p1\begin{aligned} f_{i,j} &= p_1\cdot f_{i,j}+p_2\cdot f_{i,j+1}+p_3\cdot f_{i+1,j}+p_4\cdot f_{i+1,j+1} + 1\\ &= \dfrac{p_2\cdot f_{i,j+1}+p_3\cdot f_{i+1,j}+p_4\cdot f_{i+1,j+1}+1}{1-p_1} \end{aligned}
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 nn time slots. In slot ii, the class is held in classroom cic_i; he may apply to move it to classroom did_i, and the application succeeds with probability pip_i. He may apply to change at most mm classes. After the class in slot ii, he walks from the current classroom to the classroom for slot i+1i+1. Given a graph with vv classrooms and ee 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 ii to slot i+1i+1 as one stage. At each stage, the destination is did_i with probability pip_i (if the change is applied) and cic_i with probability 1−pi1-p_i. At most mm changes may be applied; only m of the did_i destinations may be selected. The goal is the minimum expected total distance over the nn stages.

Let fi,j,0/1f_{i,j,0/1} be the minimum expected distance after reaching the classroom for slot ii, having used jj classroom-change opportunities including this slot, and choosing not to change (0) or to change (1) in this slot. The answer is min⁡{fn,i,0,fn,i,1},i∈[0,m]\min \{f_{n,i,0},f_{n,i,1}\} ,i\in[0,m]. The boundary conditions are f1,0,0=f1,1,1=0f_{1,0,0}=f_{1,1,1}=0.

Consider the transitions for fi,j,0/1f_{i,j,0/1}:

  • If this stage does not use a change, fi,j,0f_{i,j,0} may come from the previous non-change state, giving fi−1,j,0+wci−1,cif_{i-1,j,0}+w_{c_{i-1},c_{i}}, or from the previous change state. Using conditional probability and the law of total probability, the latter is fi−1,j,1+wdi−1,ci⋅pi−1+wci−1,ci⋅(1−pi−1)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}). Thus,
fi,j,0=min(fi−1,j,0+wci−1,ci,fi−1,j,1+wdi−1,ci⋅pi−1+wci−1,ci⋅(1−pi−1))\begin{aligned} f_{i,j,0}=min(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})) \end{aligned}
  • If this stage uses a change, giving fi,j,1f_{i,j,1}, it may likewise come from either the previous non-change state or the previous change state. Multiply by (1−pi)(1-p_i) when the previous application fails and by pip_i 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 n×mn \times m grid. A robot starts at row xx, column yy. 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 m=1m=1, the robot stays in place with probability 12\dfrac{1}{2} and moves down one cell with probability 12\dfrac{1}{2}, so the answer is 2⋅(n−x)2\cdot (n-x). Let fi,jf_{i,j} be the expected number of steps for a robot starting at row i, column j to reach row nn. The terminal states are fn,j=0f_{n,j}=0. Since the robot chooses to stay, move left, move right, or move down with equal probability, the transitions for fi,jf_{i,j} are:

  • fi,1=13⋅(fi+1,1+fi,2+fi,1)+1f_{i,1}=\dfrac{1}{3}\cdot(f_{i+1,1}+f_{i,2}+f_{i,1})+1
  • fi,j=14⋅(fi,j+fi,j−1+fi,j+1+fi+1,j)+1f_{i,j}=\dfrac{1}{4}\cdot(f_{i,j}+f_{i,j-1}+f_{i,j+1}+f_{i+1,j})+1
  • fi,m=13⋅(fi,m+fi,m−1+fi+1,m)+1f_{i,m}=\dfrac{1}{3}\cdot(f_{i,m}+f_{i,m-1}+f_{i+1,m})+1

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:

  • 2fi,1−fi,2=3+fi+1,12f_{i,1}-f_{i,2}=3+f_{i+1,1}
  • 3fi,j−fi,j−1−fi,j+1=4+fi+1,j3f_{i,j}-f_{i,j-1}-f_{i,j+1}=4+f_{i+1,j}
  • 2fi,m−fi,m−1=3+fi+1,m2f_{i,m}-f_{i,m-1}=3+f_{i+1,m}

Because the recurrence is evaluated backwards, every fi+1,jf_{i+1,j} is already known. There are mm columns, so the right-hand side is an mm-row column vector, while the left-hand side is an mm-row, mm-column matrix. The augmented matrix therefore has mm rows and m+1m+1 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;
}

Exercises

References

Kuangbin's probability-DP notes

This content comes from OI Wiki: Probability DP. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.