Problem. items, item has weight and value . Choose a subset of total weight maximising the total value.

NP-hard, but only weakly β€” the DP is polynomial in the value of , not in its number of bits. That is exactly why an FPTAS exists.

The DP β€”

with the weight loop running downward so each item is used once.

long long knapsack(int n, int W, vector<int>& w, vector<long long>& v) {
    vector<long long> dp(W + 1, 0);
    for (int i = 0; i < n; i++)
        for (int cap = W; cap >= w[i]; cap--)
            dp[cap] = max(dp[cap], dp[cap - w[i]] + v[i]);
    return dp[W];
}

time, space. See Knapsack for the full family.

When is huge but the values are small

Invert the DP: index by value, store the minimum weight.

long long V = accumulate(v.begin(), v.end(), 0LL);
vector<long long> dp(V + 1, LLONG_MAX / 2);
dp[0] = 0;
for (int i = 0; i < n; i++)
    for (long long val = V; val >= v[i]; val--)
        dp[val] = min(dp[val], dp[val - v[i]] + w[i]);
for (long long val = V; val >= 0; val--) if (dp[val] <= W) return val;

. Always check which of and is smaller β€” the constraints usually make one of them tractable, and this inversion is the intended solution surprisingly often.

The decision table

SituationMethodCost
standard DP
value-indexed DP
Boolean (subset sum)bitset
, both hugemeet in the middle
Items divisiblegreedy by
Unbounded copiesweight loop upward
copies eachbinary splitting
copies, need monotone deque per residue class
approximationscale the values, then DP
Few distinct weightsDP over counts
Two constraints (weight and volume)dp[w][v]

The FPTAS

Round the values down to multiples of , then run the value-indexed DP. The rounding loses at most in total.

double K = eps * maxValue / n;
for (auto& item : items) item.v = (long long)(item.v / K);
// value range is now O(n / eps), so the DP is O(n^2 / eps)

-approximate in time polynomial in both and β€” the definition of an FPTAS. Knapsack is the standard example, and it exists precisely because the problem is weakly (not strongly) NP-hard.

Fractional knapsack β€” the easy cousin

If items may be split, greedy by value density is optimal: take items in that order, splitting the last.

sort(items.begin(), items.end(), [](auto& a, auto& b) {
    return (double)a.v / a.w > (double)b.v / b.w;         // or a.v * b.w > b.v * a.w
});

Compare with cross-multiplication rather than floating-point division to stay exact. The exchange argument is one line: swapping any two out of density order cannot help.

This is the LP relaxation of 0/1 knapsack, and its value is the standard bound for branch and bound.

Reconstruction

// needs the full 2D table
int cap = W;
for (int i = n - 1; i >= 0; i--)
    if (dp[i + 1][cap] != dp[i][cap]) { chosen.push_back(i); cap -= w[i]; }

With the 1D array this is impossible β€” that is the cost of the space optimisation. If you need the items, either keep the 2D table or store a take[i][cap] bitset ( bits, which is often affordable).

Why it is worth knowing

Knapsack is the reference point for the whole DP-versus-NP-hardness discussion:

  • it is NP-hard, yet has a simple pseudo-polynomial DP;
  • the DP can be inverted when the other dimension is smaller;
  • it admits an FPTAS, unlike strongly NP-hard problems;
  • its fractional relaxation is greedy-solvable and bounds the integral optimum.

Those four facts together are the standard illustration of what β€œweakly NP-hard” means in practice.

See also: Knapsack Β· Subset Sum Β· Approximation Algorithms