DSA sheet / Dynamic programming

Book Shop

medium about 25 min dpknapsack0-1-knapsack
Open on CSES

Do these first: Minimizing Coins

The problem in brief

A shop has n books (n up to 1000). Book i has a price and a number of pages. You can buy each book at most once and have a budget x (up to 100 000). Print the maximum total number of pages you can get without exceeding the budget.

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

This is the 0/1 knapsack: each item is taken or left. Exponential search is replaced by a DP whose state is (items considered so far, money available) and whose value is the best pages. The transition is a choice: skip the item, or take it. dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - price_i] + pages_i).

Notice that dp[i] depends only on dp[i - 1], so one array over w suffices, as long as you update it in an order that lets each item be used at most once. If you iterate w upward, dp[w - price] might already include book i, effectively allowing it to be bought many times (that is unbounded knapsack, the coin-change pattern from earlier). Iterating w downward guarantees dp[w - price] still describes the state before book i. The upward/downward choice is the difference between “unlimited copies” and “at most once”.

Intuition

Keep a table “best pages if I have w rupees to spend”. For each new book, walk the table from the richest budget down to the cheapest, and whenever buying this book improves what you could get with that budget, update it. Going from the rich end guarantees you never buy the same book twice in one pass.

Approach
  1. dp[w] = 0 for all w from 0 to x (with no books, spending nothing gives no pages; it is fine to leave leftover money).
  2. For each book (price p, pages s): for w from x down to p: dp[w] = max(dp[w], dp[w - p] + s).
  3. Print dp[x].

Pitfalls: iterate w downward; totals are at most 1000 books * pages up to 1000 = 10^6, which fits in 32-bit; work is n * x = up to 10^8.

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> price(n), pages(n);
    for (auto &p : price) scanf("%d", &p);
    for (auto &s : pages) scanf("%d", &s);
    vector<int> dp(x + 1, 0);
    for (int i = 0; i < n; i++) {
        for (int w = x; w >= price[i]; w--) {
            dp[w] = max(dp[w], dp[w - price[i]] + pages[i]);
        }
    }
    printf("%d\n", dp[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