The archetypal DP family. Given items with weights and values and a capacity, maximise value.

0/1 Knapsack — each item at most once

long long knapsack01(int n, int W, vector<int>& wt, vector<long long>& val) {
    vector<long long> dp(W + 1, 0);
    for (int i = 0; i < n; i++)
        for (int w = W; w >= wt[i]; w--)              // DOWN: previous row
            dp[w] = max(dp[w], dp[w - wt[i]] + val[i]);
    return dp[W];
}

time, space. Pseudo-polynomial — it is exponential in the number of bits of , which is why knapsack is NP-hard despite this simple algorithm.

Unbounded Knapsack — unlimited copies

for (int i = 0; i < n; i++)
    for (int w = wt[i]; w <= W; w++)                  // UP: current row
        dp[w] = max(dp[w], dp[w - wt[i]] + val[i]);

The loop direction is the entire difference between the two problems. Downward reads the previous row (item used at most once); upward reads the current row (item reusable).

Bounded Knapsack — at most copies

Naive is . Two better ways:

Binary splitting. Replace copies of an item by items of multiplicity items that can form any count up to . Then run 0/1 knapsack.

for (int k = 1; c > 0; k <<= 1) {
    int take = min(k, c);
    items.push_back({wt * take, val * take});
    c -= take;
}

.

Monotone deque. Group weights by residue mod and use a sliding-window maximum. — optimal, but longer to write.

Fractional Knapsack — items divisible

Greedy, not DP: sort by value density and take greedily, splitting the last item. . This is the one knapsack variant that is not NP-hard, and the exchange argument proving it is a good example of greedy correctness.

The variant table

VariantRecurrence changeComplexity
0/1weight loop down
Unboundedweight loop up
Bounded ( copies)binary splitting
Fractionalgreedy by density
Count the ways instead of maximisingdp[w] += dp[w - wt]
Subset sum (feasibility only)boolean, use a bitset
Exactly fill capacityinitialise dp[0]=0, rest
Minimum items to reach dp[w] = min(dp[w-wt]+1)
Small , huge valuesswap the roles: dp[value] = min weight
Huge , few items ()meet in the middle
Multi-dimensional (weight and volume)dp[w][v]
knapsacksdp[w1][w2]... — exponential in

The “swap the roles” row is the one people forget: when is but , index the DP by value and store the minimum weight needed. Always check which quantity is small.

Reconstructing the chosen items

Keep the full 2D table (or a take[i][w] bitset) and walk backwards:

int w = W;
for (int i = n - 1; i >= 0; i--)
    if (dp[i + 1][w] != dp[i][w]) { chosen.push_back(i); w -= wt[i]; }

With only the 1D array, this is impossible — the trade-off for the memory saving.

See also: Coin Change · Partition Problems · Bitset Optimization