01 Backpack
01 backpack refers to a topic template
If there are many things, each thing can only be loaded into a backpack once, and the backpack has an upper volume limit
This is the classic 01 knapsack problem
When we subconsciously recite the template
Let's first think about the violent solution to the 01 knapsack problem
In the 01 backpack, each item is either taken or not taken, so there are 2^n cases in total
This is the practice of backtracking + explosive search
In order to optimize the time complexity, we introduce dynamic programming (DP)
To explain dynamic programming
I decided to introduce the concept of a DP Trilogy (from code Capriccio)
- Determine the meaning of dp array (dp table) and subscript
- Determine recurrence formula
- How to initialize dp array
- Determine traversal order
- Example derivation dp array
The following is the template question of 01 backpack
There are N items and a backpack with a capacity of V. Each item can only be used once.
The volume of article i is vi and the value is wi
Solve which items are loaded into the backpack, so that the total volume of these items does not exceed the backpack capacity, and the total value is the largest. Output maximum value.
Input format
The first line contains two integers, N and V, separated by spaces, representing the number of items and the volume of the backpack respectively.
Next, there are N lines, with two integers VI and WI in each line, separated by spaces, representing the volume and value of the i-th article respectively.
Output format
Output an integer representing the maximum value.
Data range
0 < N , V ≤ 1000 0<N,V≤1000 0<N,V≤1000
0 < v i , w i ≤ 1000 0<vi,wi≤1000 0<vi,wi≤1000
sample input
4 5 1 2 2 4 3 4 4 5
Output example:
8
The simple approach is like this
int n, V; cin >> n >> V; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; for(int i = 1; i <= n; i++) for(int j = 1; j <= V; j++) { if(j >= v[i]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]); else dp[i][j] = dp[i-1][j]; } cout << dp[n][V] << endl;
For the knapsack problem, the state can be compressed
When using a two-dimensional array, the recursive formula is: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); In fact, we found that if dp[i-1]Copy that layer directly to dp[i] The expression can be: dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
One dimensional method after compression
int n, V; cin >> n >> V; for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; for(int i = 1; i <= n; i++) for(int j = V; j >= w[i]; j--) dp[j] = max(dp[j], dp[j - w[i]] + value[i]); cout << dp[V] << endl;
We performed a DP five part analysis on 01 backpack
1. Determine the definition of dp array
In the one-dimensional dp array, dp[j] represents the maximum value of the item carried when the volume is j
2. Recurrence formula
dp[j] = max(dp[j], dp[j - weight[i]] + value[j]);
3. Initialization of array
With regard to initialization, we should consider points 1 and 2. In practical problems, we usually think about 3 and 4 together
Because dp[j] means that the maximum value of a backpack with a capacity of j is dp[j]
Then when j = 0, nothing can be installed. dp[0] = 0;
Let's look at the recursive formula
Because the meaning of dp array is the maximum value, when the value is not negative, the non-0 subscript can be initialized to 0
However, when the value is negative, the non-zero subscript must be initialized to negative infinity, otherwise the initial value will cover the maximum value
//How cpp initializes an array to negative infinity memset(d, 0x8f, sizeof(d));
4. Order of array traversal
Different from some linear DP problems, knapsack problem does not directly give the recursive direction in the recursive formula
for(int i = 1; i <= n; i++) for(int j = V; j >= w[i]; j--) dp[j] = max(dp[j], dp[j-w[i]] + value[i]);
Here we suddenly find that the traversal writing method of rolling array is somewhat different from that of two-dimensional array
Why is the loop order of the embedded loop from large to small?
A: the reverse order traversal is to ensure that each item is only put into the backpack once
There may be some difficulties here
Let's take an example: weight w[0] = 1, value[0] = 15;
Positive order traversal:
dp[1] = dp[1 - weight[0]] + value[0] = 15 dp[2] = dp[2 - weight[0]] + value[0] = 30
Item 0 has been added twice. How could it be?
Traversal in reverse order:
dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp Array already initialized to 0) dp[1] = dp[1 - weight[0]] + value[0] = 15
When traversing from back to front, the status obtained each time will not overlap with the status obtained before, so as to ensure that each item is added once
Then the question comes again. Why don't you need to traverse in reverse order in a two-dimensional array?
Because dp[i] [j] in the two-dimensional array is obtained through dp[i-1] [j], the of this layer will not be covered
It's difficult to speak here. It's recommended to practice to get real knowledge
Let's take a look at the order of the two nested for loops. In the code, we first traverse the items nested and traverse the backpack capacity. Can we traverse the backpack capacity nested and traverse the items first?
Da baa!
Because of the writing of one-dimensional dp, the backpack capacity must be traversed in reverse order (the reason has been mentioned above). If the traversed backpack capacity is placed on the upper layer, each dp[j] will only put one item, that is, only one item is placed in the backpack.
If you can't understand it, it is recommended to reverse the for loop and output the dp array for observation
5. Example derivation
Weight value
Item 0 1 15
Item 1 3 20
Item 2 4 30
One dimensional dp, use item 0, item 1 and item 2 to traverse the backpack respectively, and the final results are as follows:
First traversal of item 0 0 | 15 | 15 | 15 | 15
Traverse item 1 0 | 15 | 15 | 20 | 35 for the second time
Traverse item 20 | 15 | 15 | 20 | 35 for the third time