IOI 1995
IOI 1995

Shopping Offers

Problem Statement A shop sells b different products (b 5). Each product i has a unit price c_i, and we wish to buy exactly q_i units of product i (q_i 5). There are s special offers (s 99). Each offer specifies a bund...

Updated May 21, 2026
Track IOI
Year 1995
Statement Rendered from TeX
TeXC++Rendered statement

Problem Statement

Rendered from the "Problem Statement" section in the LaTeX write-up.

A shop sells $b$ different products ($b \le 5$). Each product $i$ has a unit price $c_i$, and we wish to buy exactly $q_i$ units of product $i$ ($q_i \le 5$).

There are $s$ special offers ($s \le 99$). Each offer specifies a bundle: for each product, how many units are included, along with a bundle price. An offer may be used as many times as desired, but we cannot buy more than the required quantity of any product.

Find the minimum total cost to buy exactly the required quantities.

Editorial

The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.

Solution Approach

State-Space DP

Since there are at most 5 products each with quantity at most 5, the state space is at most $6^5 = 7{,}776$ states. Define: \[ \mathrm{dp}[q_1][q_2][q_3][q_4][q_5] = \text{minimum cost to buy exactly } (q_1, q_2, \ldots, q_5) \text{ items.} \]

Base Case

$\mathrm{dp}[0][0][0][0][0] = 0$.

Transitions

For each state $(q_1, \ldots, q_b)$ with at least one $q_i > 0$:

  1. \textbf{Buy one unit of product $i$:} If $q_i > 0$, transition from $(q_1, \ldots, q_i - 1, \ldots, q_b)$ with cost increase $c_i$.

  2. \textbf{Use offer $j$:} If $q_i \ge o_{j,i}$ for all $i$, transition from $(q_1 - o_{j,1}, \ldots, q_b - o_{j,b})$ with cost increase equal to the offer price.

  3. We compute the DP bottom-up, iterating through all quantity tuples in lexicographic order. Each state takes its value as the minimum over all valid predecessors.

C++ Solution

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int b; // number of products (1..5)
int price[6]; // unit price for each product (1-indexed)
int want[6];  // desired quantity for each product
int code[6];  // product code mapping

int s; // number of offers
struct Offer {
    int qty[6]; // quantity of each product in offer (1-indexed)
    int cost;
};
Offer offers[100];

int dp[6][6][6][6][6];

int main() {
    scanf("%d", &s);

    // Read offers (product codes used directly for now)
    for (int i = 0; i < s; i++) {
        int nk;
        scanf("%d", &nk);
        memset(offers[i].qty, 0, sizeof(offers[i].qty));
        for (int j = 0; j < nk; j++) {
            int c, q;
            scanf("%d %d", &c, &q);
            offers[i].qty[c] = q; // store by product code
        }
        scanf("%d", &offers[i].cost);
    }

    scanf("%d", &b);
    for (int i = 1; i <= b; i++)
        scanf("%d %d %d", &code[i], &want[i], &price[i]);

    // Remap offer quantities from product codes to indices 1..b
    Offer mapped[100];
    for (int i = 0; i < s; i++) {
        mapped[i].cost = offers[i].cost;
        memset(mapped[i].qty, 0, sizeof(mapped[i].qty));
        for (int j = 1; j <= b; j++)
            mapped[i].qty[j] = offers[i].qty[code[j]];
    }

    // Initialize DP to infinity
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0][0][0][0] = 0;

    // Set up quantity bounds
    int w[6];
    for (int i = 1; i <= 5; i++)
        w[i] = (i <= b) ? want[i] : 0;

    // Bottom-up DP over all quantity tuples
    for (int q1 = 0; q1 <= w[1]; q1++)
    for (int q2 = 0; q2 <= w[2]; q2++)
    for (int q3 = 0; q3 <= w[3]; q3++)
    for (int q4 = 0; q4 <= w[4]; q4++)
    for (int q5 = 0; q5 <= w[5]; q5++) {
        if (q1 == 0 && q2 == 0 && q3 == 0 && q4 == 0 && q5 == 0)
            continue; // base case already set

        int best = 0x3f3f3f3f;
        int qq[6] = {0, q1, q2, q3, q4, q5};

        // Option 1: buy a single unit of product j
        for (int j = 1; j <= b; j++) {
            if (qq[j] > 0) {
                int prev[6] = {0, q1, q2, q3, q4, q5};
                prev[j]--;
                int val = dp[prev[1]][prev[2]][prev[3]][prev[4]][prev[5]];
                if (val < 0x3f3f3f3f)
                    best = min(best, val + price[j]);
            }
        }

        // Option 2: use an offer
        for (int o = 0; o < s; o++) {
            bool ok = true;
            int prev[6] = {0, q1, q2, q3, q4, q5};
            for (int j = 1; j <= b; j++) {
                prev[j] -= mapped[o].qty[j];
                if (prev[j] < 0) { ok = false; break; }
            }
            if (ok) {
                int val = dp[prev[1]][prev[2]][prev[3]][prev[4]][prev[5]];
                if (val < 0x3f3f3f3f)
                    best = min(best, val + mapped[o].cost);
            }
        }

        dp[q1][q2][q3][q4][q5] = best;
    }

    printf("%d\n", dp[w[1]][w[2]][w[3]][w[4]][w[5]]);
    return 0;
}

Correctness

The DP processes states in non-decreasing order of the quantity tuple (lexicographically). Every predecessor state $(q_1, \ldots, q_i - 1, \ldots, q_b)$ or $(q_1 - o_{j,1}, \ldots, q_b - o_{j,b})$ is componentwise $\le$ the current state, so it has already been computed when needed. The base case $\mathrm{dp}[\mathbf{0}] = 0$ is correct, and each transition adds exactly the cost of one purchase, so by induction the DP computes the minimum cost for every reachable state.

Complexity Analysis

  • Time complexity: $O(Q^b \cdot (b + s))$, where $Q = \max(q_i) + 1 \le 6$, $b \le 5$, and $s \le 99$. With these bounds: $6^5 \times 104 \approx 810{,}000$ operations.

  • Space complexity: $O(Q^5) = O(7{,}776)$ for the DP table.

Code

C++ solution used for this page.

C++

Clean code view with a raw-file link when you want the original source.

Raw file
// IOI 1995 - Shopping Offers
// State-space DP over product quantities (at most 6^5 = 7776 states)
// Time: O(Q^b * (b + s)), Space: O(Q^b)
#include <bits/stdc++.h>
using namespace std;

int b; // number of products (1..5)
int price[6]; // unit price for each product (1-indexed)
int want[6];  // desired quantity for each product
int code[6];  // product code mapping

int s; // number of offers
struct Offer {
    int qty[6]; // quantity of each product in offer
    int cost;
};
Offer offers[100];

int dp[6][6][6][6][6];

int main() {
    scanf("%d", &s);

    // Read offers
    for (int i = 0; i < s; i++) {
        int nk;
        scanf("%d", &nk);
        memset(offers[i].qty, 0, sizeof(offers[i].qty));
        for (int j = 0; j < nk; j++) {
            int c, q;
            scanf("%d %d", &c, &q);
            offers[i].qty[c] = q;
        }
        scanf("%d", &offers[i].cost);
    }

    scanf("%d", &b);
    for (int i = 1; i <= b; i++)
        scanf("%d %d %d", &code[i], &want[i], &price[i]);

    // Remap offer quantities to product indices 1..b
    Offer mapped[100];
    for (int i = 0; i < s; i++) {
        mapped[i].cost = offers[i].cost;
        memset(mapped[i].qty, 0, sizeof(mapped[i].qty));
        for (int j = 1; j <= b; j++)
            mapped[i].qty[j] = offers[i].qty[code[j]];
    }

    // Initialize DP with large value
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0][0][0][0] = 0;

    int w[6] = {};
    for (int i = 1; i <= 5; i++)
        w[i] = (i <= b) ? want[i] : 0;

    for (int q1 = 0; q1 <= w[1]; q1++)
    for (int q2 = 0; q2 <= w[2]; q2++)
    for (int q3 = 0; q3 <= w[3]; q3++)
    for (int q4 = 0; q4 <= w[4]; q4++)
    for (int q5 = 0; q5 <= w[5]; q5++) {
        if (q1 == 0 && q2 == 0 && q3 == 0 && q4 == 0 && q5 == 0) continue;

        int best = 0x3f3f3f3f;
        int qq[6] = {0, q1, q2, q3, q4, q5};

        // Buy single items
        for (int j = 1; j <= b; j++) {
            if (qq[j] > 0) {
                int prev[6] = {0, q1, q2, q3, q4, q5};
                prev[j]--;
                best = min(best,
                    dp[prev[1]][prev[2]][prev[3]][prev[4]][prev[5]] + price[j]);
            }
        }

        // Use offers
        for (int o = 0; o < s; o++) {
            bool ok = true;
            int prev[6] = {0, q1, q2, q3, q4, q5};
            for (int j = 1; j <= b; j++) {
                prev[j] -= mapped[o].qty[j];
                if (prev[j] < 0) { ok = false; break; }
            }
            if (ok) {
                best = min(best,
                    dp[prev[1]][prev[2]][prev[3]][prev[4]][prev[5]] + mapped[o].cost);
            }
        }

        dp[q1][q2][q3][q4][q5] = best;
    }

    printf("%d\n", dp[w[1]][w[2]][w[3]][w[4]][w[5]]);
    return 0;
}

Source Files and Assets

Raw files are still available here when you want the original TeX, C++, or statement assets.

Show raw files