Hard · DP
0/1 Knapsack
You have seen this table before: best[c] = best answer with capacity c. Coin change filled it top-down because each coin had unlimited supply; here each item exists once, and that single fact flips the inner loop from forward to backward. One loop direction is the difference between renting and owning.
At a glance<code>best[c] = max value with capacity c</code>; per item, loop c from W <em>down</em> to w: <code>best[c] = max(best[c], best[c-w] + v)</code>. Backward = each item once.
Problem
Input: The first line has n and W (1 ≤ n ≤ 20, 1 ≤ W ≤ 1000). Then n lines: w v, the item's weight (1 ≤ w ≤ 1000) and value (1 ≤ v ≤ 1000).
Output: The maximum total value achievable with total weight ≤ W, taking each item at most once.
Samples:
| Input | Output |
|---|---|
1 6 | 5 |
3 5 | 7 |
The first sample is the reuse detector: one item of weight 3 and value 5 in a bag of 6. Taking it twice would give 10 — the correct answer is 5, and only the backward loop gets it right.
Your solution
Custom input for "Run my input"
Judging runs in your browser against hidden tests. A runaway loop is cut off after 3 seconds — still, make sure every loop has an exit. Your code is saved in this browser as you type.
Stuck? Hints
Hint 1 — Why backward, in one sentence
best[c-w] + v must mean "best without this item". Sweeping c downward, every best[c-w] you read is still from the previous item's row — untouched, so the item cannot be counted twice. Sweeping upward, best[c-w] was already updated this round: the item sneaks in twice, and the first sample (answer 10 instead of 5) catches it.
Hint 2 — Zero the table yourself
best is global, and on this engine global arrays are not auto-zeroed (the lesson from Matrix Multiplication). One loop: for (c = 0; c <= W; c++) best[c] = 0;. Empty capacity legitimately holds value 0 — that base case is what the whole table grows from.
Hint 3 — Items that do not fit
An item heavier than W is legal input and must simply be skipped — the backward loop from W down to w[i] does this automatically, because the loop body never runs when w[i] > W. Resist the urge to filter the input; the loop bounds already are the filter.
Solution (try it first!)
#include <iostream>
using namespace std;
int best[1005];
int main() {
int n, W;
cin >> n >> W;
int w[20], v[20];
for (int i = 0; i < n; i++) cin >> w[i] >> v[i];
for (int c = 0; c <= W; c++) best[c] = 0;
for (int i = 0; i < n; i++) {
for (int c = W; c >= w[i]; c--) {
if (best[c - w[i]] + v[i] > best[c]) {
best[c] = best[c - w[i]] + v[i];
}
}
}
cout << best[W] << endl;
return 0;
}
Flip the inner loop forward and you get Coin Change — unlimited supply, the same line of code wearing the other convention. That symmetry is the family tree of knapsack problems in one picture: 0/1 here, unbounded there, and with one more dimension (counts per item) the bounded version. The 2^n brute force you met in Generate Subsets is the fallback when W is astronomically large but n stays tiny.
Deep dive
Why this works
best[c] = the best value achievable with capacity c using the items processed so far. The item loop processes items one at a time; the capacity loop must run backward so every best[c-w] it reads still belongs to the previous row — 'without this item'. Backward is what makes it 0/1; forward would let an item pair with itself, which is exactly unbounded knapsack (coin change).
The cost
O(n × W) time — 20 × 1000 = 20,000 transitions — and O(W) space. The alternative (best per item-subset) would be 2^20 tables; the 1D rolling trick is what collapses the item dimension.
Where it goes wrong
- Looping capacity forward reuses items: the first sample (one item, answer 10 instead of 5) is built to catch precisely this.
- Trusting a zero-initialized global: this engine does not zero arrays;
best[c] = 0for all c must be explicit. - Swapping w and v on input or in the transition: best[c-w] uses weight, the added term uses value — one swapped symbol inverts the problem silently.
Next step
Forward the loop and it is Coin Change — the unbounded sibling. Backward here is 0/1; per-item counts in between make bounded knapsack. The brute force of Generate Subsets covers tiny n with huge W.
Two things to remember
- Backward capacity loop = each item once; forward = unlimited.
- Zero the rolling table explicitly; INF/0 sentinels are the DP's load-bearing walls.
🐞 This version fails — open it and try
Capacity loop runs forward — every item pairs with itself
#include <iostream>
using namespace std;
int best[1005];
int main() {
int n, W;
cin >> n >> W;
int w[20], v[20];
for (int i = 0; i < n; i++) cin >> w[i] >> v[i];
for (int c = 0; c <= W; c++) best[c] = 0;
for (int i = 0; i < n; i++) {
for (int c = w[i]; c <= W; c++) { // bug: forward loop reuses items
if (best[c - w[i]] + v[i] > best[c]) {
best[c] = best[c - w[i]] + v[i];
}
}
}
cout << best[W] << endl;
return 0;
}