背包問題之01背包 全詳解(最淺顯易懂)


01背包是一種非常經典的動態規划問題,這里對01背包問題進行詳細解讀。


01背包問題題目描述

N N 件物品和一個容量為 V V 的背包。第 i i 件物品的體積是 c [ i ] c[i] ,價值是 w [ i ] w[i] ,求將哪些物品裝入背包可使價值總和最大。

01背包問題解析

對於所有的動態規划問題,第一步都是確定狀態。我們定義狀態 d p [ i ] [ j ] dp[i][j] 是表示目前正在枚舉第 i i 個物品,目前已取的總體積為 j j ,最大價值為 d p [ i ] [ j ] dp[i][j]
第二步自然是找狀態轉移方程
首先我們注意一點:物品只有取和不取兩種選擇,這是符合我們日常生活的。狀態轉移方程就需要從這里為突破口來思考:
(1):假如我們不取這個物品,那么 d p [ i ] [ j ] dp[i][j] 肯定是能從上一個物品,同樣體積轉移過來的,所以 d p [ i ] [ j ] = d p [ i 1 ] [ j ] dp[i][j] = dp[i - 1][j]
(2):假如我們取這個物品,那么 d p [ i ] [ j ] dp[i][j] 是從什么情況轉移呢?思考一下,首先可以得出, d p [ i ] [ j ] dp[i][j] 肯定可以從上一個物品轉移過來,那可以從什么體積轉移呢?我們注意到,對於 d p [ i ] [ j ] dp[i][j] ,它的當前取到的體積為 j j ,由於我們取了這個物品,所以上一個物品的體積為 j c [ i ] j - c[i] 。所以我們可以得出, d p [ i ] [ j ] dp[i][j] 可以從 d p [ i 1 ] [ j c [ i ] ] dp[i - 1][j - c[i]] 轉移過來,由於我們取了這個物品,所以還要加上這個物品的價值 w [ i ] w[i]
所以我們可以得出01背包的狀態轉移方程(很重要,盡量背下來!!)
j > = c [ i ] j >= c[i] 時, d p [ i ] [ j ] = m a x ( d p [ i 1 ] [ j ] , d p [ i 1 ] [ j c [ i ] ] + w [ i ] ) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i])
j < c [ i ] j <c[i] 時, d p [ i ] [ j ] = d p [ i 1 ] [ j ] dp[i][j] = dp[i - 1][j]

上代碼:

#include <iostream>

#include <cstring>

#include <algorithm>

using namespace std;

int dp[21][1010];
int w[21], c[21];
int main() {
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++) {
        cin >> w[i] >> c[i];
    }
    for (int i = 1; i <= N; i++) {
        for (int j = 0; j <= V; j++) {
            if (j >= c[i]) {
                dp[i][j] = max(dp[i - 1][j - c[i]] + w[i], dp[i - 1][j]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    cout << dp[N][V] << endl;
    return 0;
}

01背包滾動數組空間優化

我們分析一下空間復雜度: O ( N V ) O(NV) ,顯然較大,我們要優化一下,用什么優化呢?
我們可以用滾動數組!
觀察轉移方程,易得 d p [ i ] [ j ] dp[i][j] 只從 d p [ i 1 ] [ j ] dp[i - 1][j] d p [ i 1 ] [ j c [ i ] ] dp[i - 1][j - c[i]] 轉移,所以可以用一個 f l a g flag 代替 i i ,用 1 f l a g 1 - flag 代替 i 1 i - 1

這樣只需要定義 d p [ 2 ] [ m a x n ] dp[2][maxn] ,大大節省了空間。

上代碼:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int dp[2][1010];
int w[21], c[21];

int main() {
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++) {
        cin >> w[i] >> c[i];
    }
    int flag = 1;
    for (int i = 1; i <= N; i++) {
        for(int j = 0;j <= V; j++) {
            if(j >= c[i]) {
                dp[flag][j] = max(dp[1 - flag][j - c[i]] + w[i], dp[1 - flag][j]);
	    } else {
                dp[flag][j] = dp[1 - flag][j];
	    }
	}
        flag = 1 - flag;
    }
    cout << dp[1 - flag][V] << endl;
    return 0;
}

01背包空間優化

這里給大家介紹真正的空間優化。

如果我們將 d p dp 數組只用來表示體積,那么我們可以讓內層循環的 j j V V 0 0 枚舉,那么當前狀態轉移方程的 d p [ j ] dp[j] d p [ j c [ i ] ] dp[j - c[i]] 由於我們沒有更新,所以仍然是計算上一輪 i 1 i - 1 個物品的,就是二維狀態下的 d p [ 1 f l a g ] [ j ] dp[1 - flag][j] d p [ i f l a g ] [ j c [ i ] ] dp[i - flag][j - c[i]] 。所以現在我們的轉移方程是:
d p [ j ] = m a x ( d p [ j ] , d p [ j c [ i ] ] + w [ i ] ) dp[j] = max(dp[j], dp[j - c[i]] + w[i])

上代碼:

#include <iostream>
#include <cstring>
using namespace std;

int dp[1010];
int w[21], c[21];

int main() {
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++) {
        cin >> w[i] >> c[i];
    }
    for (int i = 1; i <= N; i++) {
		for (int j = V; j >= c[i]; j--) {
            dp[j] = max(dp[j - c[i]] + w[i], dp[j]);
		}
    }
    cout << dp[V] << endl;
    return 0;
}

課后習題

采葯
開心的金明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM