首先將這個問題抽象:有乙個容量為 v 的揹包,和一些物品。這些物品分別有兩個屬性,體積 w 和價值 v,每種物品只有乙個。要求用這個揹包裝下價值盡可能多的物品,求該最大價值,揹包可以不被裝滿。
因為最優解中,每個物品都有兩種可能的情況,即在揹包中或者不存在(揹包中有 0 個該物品或者 1 個),所以把這個問題稱為 0-1 揹包問題。在該題中,揹包的容積和物品的體積等效為總共可用的時間和採摘每個草藥所需的時間。
在眾多方案中求解最優解,是典型的動態規劃問題。為了用動態規劃來解決該問題,用dp[i][j]
表示在總體積不超過 j 的情況下,前 i 個物品所能達到的最大價值。初始時,dp[0][j](0<=j<=v)
為 0。依據每種物品是否被放入揹包,每個狀態有兩個狀態轉移的**。
若物品 i 被放入揹包,設其體積為 w,價值為 v,則dp[i][j] = dp[i - 1][j - w] + v
。即在總體積不超過 j-w 時前 i-1 件物品可組成的最大價值的基礎上再加上i 物品的價值v;若物品不加入揹包,則dp[i][j] = dp[i-1][j]
,即此時與總體積不超過 j 的前 i-1 件物品組成的價值最大值等價。選擇它們之中較大的值成為狀態dp[i][j]
的值。綜上所述,0-1 揹包的狀態轉移方程為:
轉移時要注意,j-w 的值是否為非負值,若為負則該轉移**不能被轉移。
#include #define inf 0x7fffffff
int max(int a,int b)
struct elist[101];
int dp[101][1001];
//記錄狀態陣列,dp[i][j]表示前i個物品
//組成的總體積不大於j的最大價值和
int main()
for(int i=0;i<=s;i++)//初始化狀態
for(int i=1;i<=n;i++)
for(int j=list[i].w-1;j>=0;j--)
}printf("%d\n",dp[n][s]);//輸出答案
}return 0;
}
觀察狀態轉移的特點,發現dp[i][j]
的轉移僅與dp[i-1][j-list[i].w]
和dp[i-1][j]
有關,即僅與二維陣列中本行的上一行有關。根據這個特點,可以將原本的二維陣列優化為一維,並用如下方式完成狀態轉移:
其中在本次更新中未經修改的dp[j-list[i].w]
與dp[j]
與原始寫法中的dp[i-1][j-list[i].w]
與dp[i-1][j]
等值。為了保證狀態正確的轉移,必須保證在每次更新中確定狀態dp[j]
時,dp[j]
和dp[j-list[i].w]
尚未被本次更新修改。考慮到j - list[i].w < j
,那麼在每次更新中倒序遍歷所有 j 的值,就能保證在確定dp[j]
的值時,dp[j - list[i].w]
的值尚未被修改,從而完成正確的狀態轉移。
#include #define inf 0x7fffffff
int max(int a,int b)
struct elist[101];
int dp[1001];
int main()
for(int i=0;i<=s;i++)
for(int i=1;i<=n;i++)
}printf("%d\n",dp[s]);
}return 0;
}
分析求解 0-1 揹包問題的演算法複雜度,其狀態數量為 ns,其中 n 為物品數量,s 為揹包的總容積,狀態轉移複雜度為 o(1),所以綜合時間複雜度為 o(ns)。經優化過後的空間複雜度僅為 o(s)(不包括儲存物品資訊所用的空間)。
0-1 揹包問題是最基本的揹包問題,其它各類揹包問題都是在其基礎上演變而來。牢記 0-1 揹包的特點:每一件物品至多只能選擇一件,即在揹包中該物品數量只有 0 和 1 兩種情況。
0-1 揹包存在乙個簡單的變化,即要求所選擇的物品必須恰好裝滿揹包。此時,設計新的狀態dp[i][j]
為前 i 件物品恰好體積總和為 j 時的最大價值,其狀態轉移與前文中所講的 0-1 揹包完全一致,而初始狀態發生變化。其初始狀態變為,dp[0][0]
為 0,而其它dp[0][j]
(前 0 件物品體積總量為 j)值均變為負無窮或不存在,經過狀態轉移後,得出dp[n][s]
即為答案。綜上所述,該變化與原始0-1 揹包的差別僅體現在初始值方面,其它各步驟均保持不變。
接著擴充套件 0-1 揹包問題,使每種物品的數量無限增加,便得到完全揹包問題:有乙個容積為 v 的揹包,同時有 n 種物品,每種物品均有各自的體積 w和價值 v,每個物品的數量均為無限個,求使用該揹包最多能裝的物品價值總和。
先按照 0-1 揹包的思路試著求解該問題。設當前物品的體積為 w,價值為 v,考慮到揹包中最多存放 v/w 件該物品,可以將該物品拆成 v/w 件,即將當前可選數量為無限的物品等價為 v/w 件體積為 w、價值為 v 的不同物品。
對所有的物品均做此拆分,最後對拆分後的所有物品做 0-1 揹包即可得到答案。但是,這樣的拆分將使物品數量大大增加,其時間複雜度為:
可見,當 s 較大同時每個物品的體積較小時其複雜度會顯著增大,固將該問題轉化為 0-1 揹包的做法較不可靠。但是由該解法可窺見 0-1 揹包的重要性,很多揹包問題均可以推到 0-1 揹包上來。
這裡提出一種時間複雜度為 o(n*s)的解法,其使用如前文中經過空間優化過的 0-1 揹包所使用的一維陣列,按如下方法進行狀態轉移:
for (int i = 1;i <= n;i ++)
}
注意到該**片段與上文中所講的 0-1 揹包相比,似乎只存在著對狀態 j 的遍歷順序有所差異,這是有原因的。在 0-1 揹包中,之所以逆序迴圈更新狀態是為了保證更新dp[j]
時,dp[j - list[i].w]
的狀態尚未因為本次更新而發生改變,即等價於由dp[i - 1][j - list[i].w]
轉移得到dp[i][j]
。逆序迴圈,保證了更新dp[j]
時,dp[j - list[i].w]
是沒有放入物品 i 時的資料(dp[i - 1][j - list[i].w]
),這是因為 0-1 揹包中每個物品至多只能被選擇一次。而在完全揹包中,每個物品可以被無限次選擇,那麼狀態dp[i][j]
恰好可以由可能已經放入物品 i 的狀態dp[i][j - list[i].w]
轉移而來,固在這裡將狀態的遍歷順序改為順序,使在更新狀態dp[j]
時,dp[j - list[i].w]
可能因為放入物品 i 而發生改變,從而達到目的。 Day of week 九度教程第7題
其大意為,輸入乙個日期,要求輸出該日期為星期幾。星期幾是以七為週期迴圈的,那麼只需要知道 1.今天是星期幾 2.今天和所給定的那天相隔幾天。利用其對7求餘數便可以知道所給定的那天是星期幾。include include define isleapyeap x x 100 0 x 4 0 x 400 ...
Sort 九度教程第11題
時間限制 1 秒 記憶體限制 128 兆 特殊判題 否 題目描述 給你n個整數,請按從大到小的順序輸出其中前m大的數。輸入 每組測試資料有兩行,第一行有兩個數n,m 0 include using namespace std bool cmp int x,int y int main 初始化,將每個...
a b 九度教程第60題
這是一例典型的考察高精度整數的題,其輸入非常巨大 1000 位 不能使用任何整數型別來直接儲存它。使用c c 首先明確高精度整數的儲存形式,常用如下結構體來儲存乙個高精度整數 struct biginteger 其中digit陣列用來儲存大整數中每若干位的數字,這裡暫且使用每4位為乙個單位儲存,si...