Getting Started 01 Knapsack Problem

foreword

Recently, I was doing quizzes in luogu, and I was deeply tortured by the backpack problem. As a konjac who just started learning dynamic programming, I can only solve simple knapsack problems. In order to deepen my understanding of this question type, I decided to organize my current knowledge of the 01 backpack. (The relevant code will be displayed using python)

What is the knapsack problem

Please see the following questions:

Xiao Ming has a backpack that can hold 500 grams of items. Now there are 5 items in front of him. Their weight and value are as follows:

Item 1: 130g 50 yuan

Item 2: 75 yuan for 200g

Item 3: 400g 200 yuan

Item 4: 800g 1000 yuan

Item 5: RMB 50 for 50g

May I ask Xiao Ming how to pack the items in front of him so that the value of the items in the backpack is the greatest?

The above is a 01 knapsack problem. Generally speaking, the knapsack problem can be described as: given a set of items, each item has its own weight and price, within the limited total weight, how do we choose to make the total weight of the items The highest price. Of course, the question stem of the knapsack problem does not necessarily have the word "backpack", nor does it necessarily have the so-called "weight" and "price". When actually doing the questions, you must review the questions and find out the implicit conditions.

01 backpack problem

This is a classic 01 knapsack problem, excerpted from Luogu P1048 Herbal Collection:

The characteristic of the 01 knapsack problem is that there are only two cases of taking or not taking each item. Taking "Collecting Herbs" as an example, each plant of medicine given out was either picked by Chenchen or not picked, and there was no such thing as picking twice or three or four times.

problem solving method

Next, I will use a simple example to explain the problem-solving method of the 01 backpack, and then we can conquer "Picking Herbs":

In order to keep the conditions as simple as possible, our Xiao Ming took out a mini backpack that can only hold 5g items, and now he wants to load these items:

Item 1: RMB 3 for 2g; Item 2: RMB 2 for 3g; Item 3: RMB 5 for 3g; Item 4: RMB 6 for 4g; Item 5: RMB 1 for 1g

Next, let us assume that the capacity of the backpack can be changed, but it cannot exceed 5g

Now we load item 1, when the backpack capacity changes between 0g and 5g, the maximum value that can be achieved can be presented in the following table

Ok, then we will load item 2 (3g 2 yuan) based on the above table;

Obviously, when the backpack capacity is 0, 1, 2, item 2 cannot be loaded, so the maximum value that these 3 grids can achieve is the same as the previous line:

So we got the first rule: when the knapsack capacity is not enough to hold the item being considered now, the value of the current grid is the same as the previous row

So what to do when the backpack capacity is 3, 4, 5; at this time, you will face a decision: whether to put item 2 or not. A very natural idea is: if the value of putting item 2 is greater than that of not putting it in, then put it, otherwise don't put it. Take the backpack with a capacity of 4 as an example. If we want to put it in, we must first save 3g of space. Let’s see how much value is left in the backpack after saving 3g.

What do you think? 4g-3g=1g, the grid corresponding to 1g in the previous row is the remaining value (red circle) after saving 3g, add this value to the value of item 2, and you can get situation A: put in item 2 , this situation reaches The value is 2 yuan

 However, putting in item 2 is not necessarily a good thing. Situation B: If you don’t put in item 2, you can get a greater value, so where to look at the value achieved if you don’t put in item 2, it is obviously the value corresponding to the previous line , you can see The value obtained without putting in item 2 is 3 yuan

So we got the second rule: When the capacity of the backpack is enough to put the item under consideration, compare the two situations of putting it in and not putting it in, and take the larger one of the two

Using these two rules we can complete the above table:

 The next items 3, 4, and 5 also take the same operation, and the following table can be obtained (it is recommended that students who are new to the knapsack problem simulate the process of generating this table on paper):

 It can be seen that the value in the lower right corner is the maximum value that Xiao Ming can obtain, which is 8 yuan. Based on this example, we can write the solution to the problem of "Collecting Herbs".

The Solution to the Example "Collecting Herbs"

In order to solve "Collecting Herbs", we can draw a table like the above example. But how to represent this form with python code? A simple and easy solution is to construct a 2-dimensional array dp[i][j] to represent, where i corresponds to the item number, and j corresponds to the backpack capacity. For example, the above table can be written as such a two-dimensional array:

dp = [[0,0,3,3,3,3],
[0,0,3,3,3,5],
[0,0,3,5,5,8],
[0,0,3,5,6,8],
[0,1,3,5,6,8]]

Let's look back at the title of "Collecting Herbs":

After reading the question, let's solve the data input first, because each herb has two attributes of "time spent" and "value", we use the list cost to record the time spent, and use the list value to record the value

T, M = map(int,input().split())
cost = []
value = []
for i in range(M):
    a, b = map(int,input().split())
    cost.append(a)
    value.append(b)

Analyzing the topic, it is found that this is a 01 knapsack problem, where T (the total time available for collecting herbs) can be regarded as the maximum capacity of the backpack, M (the number of herbs) can be regarded as the number of items, and the time spent on each herb can be viewed as the "weight" of the item. So we can construct a two-dimensional array of size (M + 1) * (T + 1):

dp = []
for i in range(M + 1):
    dp.append([0] * (T + 1))

In line 0 of dp, because there is no item numbered 0, we have an item 0 by default: spend time 0 and value 0, just change the previous cost and value. At this point, use cost[i] and value[i] to get the property of the herb number i

#将前面的对应代码更改成这样:
cost = [0]
value = [0]

Starting from the first row of dp, fill in the value of each grid according to the previously introduced rule 1 and rule 2

for i in range(1,len(dp)):
    for j in range(len(dp[i])):
        if cost[i] < j:
            dp[i][j] = dp[i-1][j]
#规则一:当背包容量不足以装下现在正在考虑的物品时,当前格子的值和上一行相同
        else:
            dp[i][j] = max(dp[i-1][j-cost[i]] + value[i], dp[i-1][j])
#规则二:当背包容量足以放入现在正在考虑的物品时,对比放与不放两种情况,取两者之中较大的一个

Finally, output dp[-1][-1] (the value in the lower right corner of the form)

print(dp[-1][-1])

algorithm optimization

constant optimization

In the process of solving the 01 knapsack problem, we found that the weight of some items has exceeded the maximum capacity of the knapsack, such as the input sample given in "Collecting Herbs":

 

 Obviously, the time spent 71 has exceeded the prescribed time 70 for collecting herbs, so it is meaningless to consider whether this herb is picked or not. So we can add a judgment on whether the data is meaningful when inputting data, and give up recording meaningless data. This is constant optimization; the code is as follows:

T, M = map(int,input().split())
cost = [0]
value = [0]
for i in range(M):
    a, b = map(int,input().split())
    if a <= T:
        cost.append(a)
        value.append(b)
dp = []
for i in range(len(cost)):
    dp.append([0] * (T + 1))
for i in range(1,len(dp)):
    for j in range(len(dp[i])):
        if cost[i] > j:
            dp[i][j] = dp[i-1][j]
        else:
            dp[i][j] = max(dp[i-1][j-cost[i]] + value[i], dp[i-1][j])
print(dp[-1][-1])

scroll array

When the capacity of the backpack is relatively large and the number of items is relatively large, using the above method we will construct a very large two-dimensional array, which takes up a lot of space. In fact, observing the entire table generation process, the value of each grid is only related to the value of the previous row. That is to say, when considering item 3, the row about item 1 can actually be deleted. When considering item 4 , the line about item 2 will not be used anymore.

Based on this idea, we can construct a one-dimensional array dp[i] and update the values ​​in it continuously. This kind of array is called a rolling array; the code is as follows:

T, M = map(int,input().split())
cost = [0]
value = [0]
for i in range(M):
    a, b = map(int,input().split())
    if a <= T:
        cost.append(a)
        value.append(b)
dp = [0] * (T + 1)
dp_ = [0] * (T + 1)
for i in range(1,len(cost)):
    for j in range(len(dp)):
        if cost[i] > j:
            dp_[j] = dp[j]
        else:
            dp_[j] = max(dp[j-cost[i]] + value[i], dp[j])
    dp = dp_.copy()
print(dp[-1][-1])

Summarize

The question example of "Collecting Herbs" only shows the most basic proposition form of 01 backpack. In actual practice, many variations will be encountered. On the basis of this question, it is necessary to draw inferences about other cases from one instance, in order to achieve "doing a question, knowing a class of questions".

Guess you like

Origin blog.csdn.net/caterpie_771881/article/details/128467682