AcWing 2 01揹包問題

2021-10-01 22:16:19 字數 3457 閱讀 6525

題目描述:

有 n 件物品和乙個容量是 v的揹包。每件物品只能使用一次。第 i件物品的體積是 vi,價值是 wi。

求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。

輸出最大價值。

輸入格式

第一行兩個整數,n,v,用空格隔開,分別表示物品數量和揹包容積。

接下來有 n行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i件物品的體積和價值。

輸出格式

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

資料範圍

0輸入樣例

4 5

1 22 4

3 44 5

輸出樣例:

8
分析:

從這題開始就進入到動態規劃的刷題了,首先回憶下動態規劃的一些概念。

動態規劃的定義:鄧公在mooc中是這麼說的,動態規劃就是先用遞迴的思想找出問題的本質,再將其等效地轉化成迭代的寫法。更常規的定義是dp是用來解決一類最優化問題的演算法思想,將乙個複雜的問題分解成若干個子問題,通過綜合子問題的最優解來得到原問題的最優解。一般使用遞迴或者遞推的寫法來實現動態規劃,遞迴的寫法又稱為記憶化搜尋。

重疊子問題:如果乙個問題可以被分解為若干個子問題,且這些子問題會重複出現,那麼稱這個問題擁有重疊子問題。需要用動態規劃記錄重疊子問題的解,避免大量重複計算。舉個簡單的例子就是斐波那契數問題,每個狀態都是由前兩個狀態轉移過來的,fib(n)的計算用到了fib(n-1)和fib(n-2),fib(n-1)的計算用到了fib(n-2)和fib(n-3),如果不去儲存中間結果,fib(n-2)就要被重複計算,斐波那契問題裡規模為n-1的子問題和規模為n-2的子問題就是重疊的,甚至可以說n-1子問題的求解完全包括了n-2子問題的求解。

最優子結構:如果乙個問題的最優解可以由其子問題的最優解有效的構造出來,那麼稱這個問題擁有最優子結構。

乙個問題必須擁有重疊子問題和最優子結構,才能使用動態規劃去解決。動態規劃的出現一方面是消除了重疊子問題的重複計算,另一方面要從子問題的解推出總問題的解,就要求問題必須要有最優子結構。

有一類動態規劃可解的問題,它可以描述成若干個有序的階段,且每個階段的狀態只和上乙個階段的狀態有關,一般把這類問題稱為多階段動態規劃問題。

狀態無後效性:當前狀態記錄了歷史資訊,一旦當前狀態確定,就不會再改變,且未來的決策只能在已有的乙個或若干個狀態的基礎上進行,歷史資訊只能通過已有的狀態去影響未來的決策。

動態規劃中的狀態與搜尋中的狀態基本類似,我們找到初始的狀態,然後要一步步轉移到最終的狀態。首先要做的就是狀態的表示,一般用陣列來表示狀態,狀態的表示取決於問題中狀態涉及的屬性。表示好狀態後,我們易得問題的初始狀態,也就是dp的邊界,然後通過狀態轉移方程的設計逐步擴散到所有的狀態,最終可以解決問題。所以dp的核心在於狀態轉移方程的設計,換而言之,狀態轉移方程就是規定了由子問題的解是如何得到問題的解的。解決問題有分而治之和減而治之兩種思想,減而治之一般只需要從規模為n-1的子問題推出規模為n的問題,而分而治之則需要合併若干個規模較小的子問題才能得出問題的解。

本題是最簡單的01揹包問題,物品有三種屬性,數量,體積和價值,需要我們求解的最優解是揹包中物品的最大價值。用陣列f來表示狀態,第一維表示問題的規模,第二維表示體積,f陣列的值表示最大價值,即f[i][j]表示前i個物品中體積不超過j狀態的最大價值。下面要解決的就是如何從規模為i - 1的問題的解推出規模為i問題的解。揹包問題的關鍵在於物品的選擇,01揹包問題每個物品要麼選要麼不選,且數量都只有乙個。還記得最初對dp的定義是用遞迴的思想找出問題的本質,假設我們現在要寫個dfs找出本題的最優解,則需要從第乙個物品逐個往後遍歷,每個物品選了加進揹包是一種狀態,不選又是一種狀態。所以規模為i的問題可能由兩種狀態轉移而來,一種是選擇了第i個物品,另一種是沒有選擇第i個物品。f[i][j]表示在前j個物品中選出總體積不超過j的物品

的最大價值,如果第i個物品沒有選擇,說明在遍歷完前i - 1個物品時,揹包中已然裝進體積不超過j的物品了,故這種狀態表示為f[i-1][j];如果選擇了第i個物品,第i個物品體積為v[i],說明在前i-1個物品中已經裝進了不超過j - v[i]體積的物品,狀態表示為f[i-1][j-v[i]]。總結一下,f[i][j]這種狀態可以由f[i-1][j]以及f[i-1][j-v[i]]這兩種狀態轉移而來。題目的要求是價值最大,f陣列儲存的永遠是價值最大的那個,故f[i][j] = max(f[i-1][j],f[i-1][j-v[i]] + w[i])。

確定了狀態轉移方程是f[i][j] = max(f[i-1][j],f[i-1][j-v[i]] + w[i])後,需要確定dp的邊界狀態,即揹包中什麼都不裝,總價值自然是0。f[0][0] = f[0][1] = ... = f[0][m] = 0。然後確定狀態轉移的方向,既然f[i][j]表示的是前i個物品的最大價值,前i個物品的狀態是由前i - 1個物品的狀態轉移過來的,那麼我們需要先求出前1個物品的所有狀態,然後是前2個物品的所有狀態...。設一共n個物品,揹包容量為m,那麼迴圈第一層是1到n,第二層是0到m。注意:在進行狀態轉移時,只有當j > v[i]時才可能裝了第i個物品,這是顯然的,如果裝了第i個物品揹包中物品體積肯定超過v[i]。具體**如下:

#include #include using namespace std;

const int maxn = 1005;

int w[maxn],v[maxn],f[maxn][maxn];

int main()

}cout以前初學dp時,最迷糊的就是挑戰程式設計競賽上面使用滾動陣列來節省dp的儲存空間了。為此,我們需要先明白狀態轉移的方向,也就是迴圈的方向,不妨將f陣列列印出來。

以f[4][5]為例,僅由f[3][1]和f[3][5]轉移而來。我們計算f陣列的順序是自左而右,自上而下。每個狀態僅用到上一行的兩個狀態,之前行的狀態都沒用了,所以不妨將f[i][j]去掉第一維,我們即f[j]在迴圈遍歷到第一行時儲存的是f[1][j],遍歷到第二行時儲存的是f[2][j],覆蓋掉了第一行的狀態值。還有個需要考慮的問題是假如我們還按照之前自左而右的f陣列填充順序的話,在算完f[4][1]時,已經覆蓋掉了f[3][1]的值,後面計算f[4][5]時便無法獲得之前f[3][1]的值了。每個狀態都只會用的上一行的兩個狀態,分別是上一行與之對齊的狀態和上一行相對偏左位置的狀態,因此,上一行右邊的狀態不會影響下一行左邊的狀態,我們可以自右而左的填充f陣列,這樣,計算f[4][5]時用到的兩個狀態都還沒被覆蓋掉。這時的一維陣列f就是滾動陣列,在不同的狀態轉移過程中表示不同的含義,有效的節省了時間和空間。具體**的變化只需要去掉上面**f的第一維,並將內層迴圈的列舉迴圈改為自右向左即可。

#include #include using namespace std;

const int maxn = 1005;

int w[maxn],v[maxn],f[maxn];

int main()

}cout<

return 0;

}

acwing 2 01揹包問題

有 n 件物品和乙個容量是 v的揹包。每件物品只能使用一次。第 i件物品的體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且價值最大。輸出最大價值。輸入格式 第一行兩個整數,n,v,用空格隔開,分別表示物品數量和揹包容積。接下來有 n行,每行兩個整數 vi,w...

acwing 2 01揹包問題

有 n 件物品和乙個容量是 v 的揹包。每件物品只能使用一次。第 i 件物品的體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。輸入格式 第一行兩個整數,n,v,用空格隔開,分別表示物品數量和揹包容積。接下來有 n 行,每行兩個整數 ...

acwing2 01揹包問題

有n n件物品和乙個容量是v v的揹包。每件物品只能使用一次。第i i件物品的體積是 v i vi,價值是 w i wi。求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。輸入格式 第一行兩個整數,n,v n,v n,v,用空格隔開,分別表示物品數量和揹包容積。...