DSA sheet / Dynamic programming

Coin Combinations II

medium about 20 min dpcountingcoin-changeunbounded-knapsack
Open on CSES

Do these first: Coin Combinations I

The problem in brief

Same setup as the previous problem: n distinct coin values (n up to 100), unlimited supply, a target x up to 10^6. But now two ways are the same if they use the same number of each coin, regardless of order. Print the number of distinct ways 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 difference between Coin Combinations I and II is entirely in how you enumerate. To count multisets you need a canonical form so each multiset is produced once. The natural one: fix an order on coin types and require the coins of a solution to be used in that order (all the 2s, then all the 3s, then all the 5s). Then a solution is exactly a choice of counts per type.

DP follows: let ways[s] count the multisets that use only the coin types processed so far. When you add a new coin type c, a multiset for total s either uses no c (already counted in ways[s]) or uses at least one c, which is a multiset for s - c over the same enlarged set, plus one more c. So ways[s] += ways[s - c], iterating s upward so that ways[s - c] already includes uses of c. Coin type on the outside, total on the inside is what makes it count unordered combinations.

The habit: to switch from counting sequences to counting sets, change the loop order so that “which item” is decided in a fixed sequence.

Intuition

Hand out coin types one at a time. After you have introduced coin 2, count ways to pay with 2s only; then introduce coin 3 and add the new ways that use 3s; and so on. Nobody ever chooses a 2 after a 3, so no arrangement is double counted.

Approach
  1. ways[0] = 1, rest 0.
  2. For each coin c (outer loop), for s from c to x (inner loop, ascending): ways[s] = (ways[s] + ways[s - c]) mod (10^9 + 7).
  3. Print ways[x].

Pitfalls: the loop nesting is the whole problem, swap it and you get the previous answer; keep sums under the modulus each step; x up to 10^6 and n up to 100 gives 10^8 steps (PyPy for Python).

Complexity

O(n * x) time, O(x) 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, x;
    scanf("%d %d", &n, &x);
    vector<int> coins(n);
    for (auto &c : coins) scanf("%d", &c);
    const int MOD = 1000000007;
    vector<int> ways(x + 1, 0);
    ways[0] = 1;
    for (int c : coins) {
        for (int s = c; s <= x; s++) {
            ways[s] += ways[s - c];
            if (ways[s] >= MOD) ways[s] -= MOD;
        }
    }
    printf("%d\n", ways[x]);
    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