2021寒假每日一題《01揹包問題》

01揹包問題

題目來源:揹包九講
時間限制:1000ms 內存限制:64mbjava

題目描述

\(N\) 件物品和一個容量是 \(V\) 的揹包。每件物品只能使用一次。
\(i\) 件物品的體積是 \(v_i\),價值是 \(w_i\)
求解將哪些物品裝入揹包,可以使這些物品的整體積不超過揹包容量,且總價值最大。
輸出最大價值。算法

輸入格式

第一行兩個整數,\(N\)\(V\),用空格隔開,分別表示物品數量和揹包容積。
接下來有 \(N\) 行,每行兩個整數 \(v_i\),\(w_i\),用空格隔開,分別表示第 \(i\) 件物品的體積和價值。shell

輸出格式

輸出一個整數,表示最大價值。數組

數據範圍

0 < \(N\),\(V\) ≤ 1000
0 < \(v_i\),\(w_i\) ≤ 1000學習

樣例輸入

4 5
1 2
2 4
3 4
4 5

樣例輸出

8

解題思路1:暴力破解

嘗試各類可能的商品組合,並找出價值最高的組合。
使用 \(N\) 位二進制字串表示物品是否放入揹包,枚舉全部的可能,而後算出每種可能的價值,取其最大值輸出。
解法不足:速度很是慢。在只有3件商品的狀況下,你須要計算8個不一樣的集合;當有4件商品的時候,你須要計算16個不一樣的集合。每增長一件商品,須要計算的集合數都將翻倍。
對於每一件商品,都有選或不選兩種可能,即這種算法的運行時間是 \(O(2^n)\)
IO競賽(例如:藍橋杯)當中,若是實在想不起來動態規劃,可使用這個方法拿到一部分分數,可是在ACM競賽當中,就不能使用這種方法了。ui

解題代碼1-Java

import java.util.*;

public class Main {
    static int getSum(int n, int v, StringBuilder binString, int[][] items) {
        int sumV = 0, sumN = 0;
        for (int j = 0; j < n; j++) {
            if (binString.charAt(j) == '1') {
                sumV += items[j][0];
                sumN += items[j][1];
            }
            if (sumV > v) {
                return -1;
            }
        }
        return sumN;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int v = input.nextInt();
        int totalV = 0, totalN = 0;
        int[][] items = new int[n][2];
        for (int i = 0; i < n; i++) {
            items[i][0] = input.nextInt();
            items[i][1] = input.nextInt();
            totalV += items[i][0];
            totalN += items[i][1];
        }
        input.close();
        if (totalV <= v) {
            System.out.println(totalN);
            return;
        }
        int sumN = 0;
        for (long i = 0; i < Math.pow(2, n); i++) {
            StringBuilder binString = new StringBuilder(Long.toBinaryString(i));
            long len = binString.length();
            while (len < n) {
                binString.insert(0, "0");
                len++;
            }
            sumN = Math.max(sumN, getSum(n, v, binString, items));
        }
        System.out.println(sumN);
    }
}

解題思路2:動態規劃

對於動態規劃算法,能夠去這篇文章裏學習:經典中的經典算法:動態規劃(詳細解釋,從入門到實踐,逐步講解)
每一個動態規劃都從一個網格開始。
在本題中,網格的各行表示商品,各列表明不一樣容量的揹包(從1到V)。
網格最初是空的。你將填充其中的每一個格子,網格填滿後,就找到了問題的答案!
好比本題樣例的網格以下圖:
圖1:初始網格spa

1、畫好網格以後,先來看第一行。

在每一個一格子你都要作出一個選擇:放不放進這一行對應的物品。
物品1所佔空間爲 \(1\) ,也就是說物品1能放進容量爲 \(1\) 的這個揹包,所以這個單元格包含物品1。因此往第一行第一列中裝入物品1,價值爲2。
圖2:填充第一行第一列
這行的其餘單元格也同樣。別忘了,這是第一行,只有一個物品可供你選擇,換而言之,你僞裝如今尚未打算放進其餘物品。
填充完以後以下圖:
圖3:填充完第一行
此時你極可能心存疑惑:原題說的是容量爲5的揹包,咱們爲什麼要考慮容量爲一、二、三、4的揹包呢?動態規劃從子問題着手,逐步解決大問題。這裏解決的子問題將幫助你解決大問題。
這行表示的是當前的最大價值,並非最終解。隨着算法往下執行,將逐步修改最大價值。.net

2、接下來看第二行。

在第二行中,你有兩種物品選擇。先看第一個單元格,容量爲1,以前裝進去的最大的價值爲2。
這一個單元格該不應放入第二個物品呢?答案顯然是不行,由於容量爲1的口袋沒法裝下佔空間2的物品。
所以第一列的最大價值保持不變。
圖4:填充第二行第一列
接下來看第二行第二列,這個格子所對應揹包的容量爲2,如今可以裝下第二個物品了,對比一下價值,比以前決定的物品1價值高,因此將原來的物品1換爲物品2。
再看後面的第三列,這個格子容量爲3,能夠同時裝下物品1和物品2,因此都放進去。
以後的列也進行同樣的操做,最後操做完第二行的結果爲:
圖5:填充完第二行3d

3、作完第二行,接下來看第三行

以一樣的方式處理物品3,物品3佔用空間3,價值爲4。
其中在第五列時,容量爲5,本來決定的爲(物品1+物品2),如今有物品3能夠選擇了,因此考慮將物品1換爲價值更高的物品3,因此此行第五列結果爲8
作完這一步就獲得了下面的網格:
圖6:填充完第三行code

4、第四行也同樣

圖7:填充完第四行

5、最終結果

能夠總結獲得一個公式:\( cell[i][j](i和j代指行和列) = 二者中較大的一項 \begin{cases} 1.上一個單元格的值(即:cell[i-1][j]的值) \\ 2.當前物品的價值+剩餘空間的價值(即:cell[i-1][j-當前物品的佔用空間] + 當前物品的價值) \end{cases} \)
你可使用這個公式來計算每一個單元格的價值,最終的網格將與前一個網格相同。
這裏回答前面拋出的問題:爲何要求解子問題?——由於你能夠合併兩個子問題的解來獲得更大問題的解。
相信看到這裏,而且親手推導過網格,應該對動態規劃的狀態轉移方程背後的邏輯有了更深的理解。如今,再回頭看01揹包問題的經典描述,並實現代碼。

6、程序實現

聲明一個數組dp[n+1][v+1]表示初始網格,首行爲0,表示不放入任何物品,同時也爲了代碼閱讀性,從下標1開始處理。
圖8:dp初始
根據第五步的公式,對於編號爲 \(i\) 的物品:

  • 若是將\(i\)放入,\(當前揹包的最大價值 = 第i號物品的價值 + 出去i號物品佔用空間後剩餘的空間所能存放的最大價值\)
    即:\(valueWith\_i = w_i + dp[i-1][j-v_i];\)
  • 若是不放入\(i\)\(當前揹包的價值 = 前i-1個物品存放在揹包中的最大價值\)
    即:\(valueWithout\_i = dp[i-1][j];\)
  • 最終,\(dp[i][j]\)的結果取二者的較大值。
    即:\(dp[i][j] = Math.max(valueWith\_i, valueWithout\_i);\)

解題代碼2-Java

import java.util.*;

public class Main {
    static int maxValue(int n, int v, int[][] items) {
        if (n == 0) {
            return 0;
        }
        int[][] dp = new int[n + 1][v + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= v; j++) {
                int valueWith_i = (j - items[i - 1][0] >= 0) ? (items[i - 1][1] + dp[i - 1][j - items[i - 1][0]]) : 0;
                int valueWithout_i = dp[i - 1][j];
                dp[i][j] = Math.max(valueWith_i, valueWithout_i);
            }
        }
        return dp[n][v];
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int v = input.nextInt();
        int totalV = 0, totalN = 0;
        int[][] items = new int[n][2];
        for (int i = 0; i < n; i++) {
            items[i][0] = input.nextInt();
            items[i][1] = input.nextInt();
            totalV += items[i][0];
            totalN += items[i][1];
        }
        input.close();
        if (totalV <= v) {
            System.out.println(totalN);
            return;
        }
        System.out.println(maxValue(n, v, items));
    }
}
相關文章
相關標籤/搜索