DSA sheet / Dynamic programming

Array Description

medium about 25 min dpcountingstate-per-value
Open on CSES

Do these first: Dice Combinations, Grid Paths I

The problem in brief

You know an array of length n (up to 100 000) whose entries are between 1 and m (m up to 100), except that some entries are unknown (marked 0). Count the ways to fill the unknowns so that every pair of adjacent entries differs by at most 1. Print the count modulo 10^9 + 7.

Try it

Tab indents; press Esc, then Tab, to leave the box. Ctrl+Enter runs.

Hints

Stuck? Reveal one hint at a time. Each nudges without giving away the next.

The walkthrough

Spoilers ahead: open a section only after you have given the hints a fair try.

How to think

The key is finding the right state. A partial filling has a lot of history, but the constraint “neighbours differ by at most 1” only ever looks at the last entry. So the state (position, last value) is a sufficient summary, and there are only n * m of those states, about 10^7.

Transition: ways[i][v] = ways[i - 1][v - 1] + ways[i - 1][v] + ways[i - 1][v + 1], but only if position i allows the value v (it is unknown, or it equals v); otherwise it is 0. Start: at position 0, ways[0][v] = 1 for every allowed v. Answer: the sum of ways[n - 1][v] over v.

The habit for counting constrained sequences: find the smallest summary of the past that determines which futures are legal (here just the last value), make that the DP dimension, and sweep position by position with a rolling array.

Intuition

Picture a token walking along the array positions and standing on a value at each step; it may stay put or move one up or one down, and at known positions it must stand on the given value. Count the walks by keeping, for each value, how many walks currently stand there.

Approach
  1. cur[v] for v in 1..m: if a[0] = 0, then 1 for every v, else 1 only for v = a[0].
  2. For each next position i: for every v allowed at i (all v if a[i] = 0, else only a[i]): next[v] = cur[v - 1] + cur[v] + cur[v + 1] (treat out-of-range as 0) mod 10^9 + 7; everything else is 0.
  3. The answer is the sum of cur[v] after the last position, mod 10^9 + 7.

Pitfalls: pad the arrays with an extra 0 slot on each side so v - 1 and v + 1 never go out of range; reduce modulo after summing the three terms (64-bit sum); a known value may make the total 0.

Complexity

O(n * m) time, O(m) memory.

Solutions

Written from scratch and checked by compiling and running each one against a brute force on random inputs. Fast input/output, the way you would submit it.

Show solutions (C++17, Python 3, Java 17, Node.js)
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    vector<int> a(n);
    for (auto &v : a) scanf("%d", &v);
    const long long MOD = 1000000007LL;
    vector<long long> cur(m + 2, 0), nxt(m + 2, 0);
    for (int v = 1; v <= m; v++) cur[v] = (a[0] == 0 || a[0] == v) ? 1 : 0;
    for (int i = 1; i < n; i++) {
        for (int v = 1; v <= m; v++) {
            nxt[v] = (a[i] == 0 || a[i] == v) ? (cur[v - 1] + cur[v] + cur[v + 1]) % MOD : 0;
        }
        swap(cur, nxt);
    }
    long long total = 0;
    for (int v = 1; v <= m; v++) total += cur[v];
    printf("%lld\n", total % MOD);
    return 0;
}

Problem: CSES Problem Set (Antti Laaksonen, University of Helsinki), CC BY-NC-SA 4.0. The explanations and code above are original (© Anupam Kumar).

Related problems

Back to the sheet