背包問題
題意:給出背包的容量端逼,以及一批物品的價值和大小揖铜,求最大價值。
01背包問題
題意
每個物品只能放入一次困介。
思路
用f[i][v]
表示,第i
個大小為v
的物品放入時的總價值蘸际。
c[i]
表示第i
個物品的價值座哩。w[i]
為第i
個物品的大小。
狀態(tài)轉(zhuǎn)移方程:f[i][v] = max(f[i-1][v], f[i-1][v-w[i]]+c[i]);
狀態(tài)轉(zhuǎn)移方程表示粮彤,取放入或者不放入第i
個物品兩種情況的最大值根穷。
空間優(yōu)化(滾動數(shù)組)
初始狀態(tài)方程的空間復雜度是O(V*W)
,可以進一步優(yōu)化导坟。
可以將空間優(yōu)化為O(2*W)
屿良,即縱向大小為2。
for(i=1; i<=N; i++){
for(j=t[i]; j<=V; j++)
f[t^1][j] = max(f[c][j-w[i]]+c[i], f[t][j]);
t ^= 1;
}
異或滾動可以在0和1之間切換惫周,可以利用上下反復更新尘惧。
空間優(yōu)化(一維數(shù)組)
既然可以用兩行進行更新,那為什么不能用一行递递。
觀察問題喷橙,兩行更新時,用上一行的前部分更新下一行的后部分登舞。
所以單行更新時要從后往前遍歷贰逾,這樣可以用前面的更新后面的。
for(i=1; i<=N; i++)
for(j=V; j>=w[i]; j--)
f[j] = max(f[j-w[i]]+c[i], f[j]);
這樣就可以用一維數(shù)組來進行更新菠秒。
可以寫成函數(shù)疙剑,封裝起來。
void ZeroOnePack(int cost, int weight){
for(int i=V; i>=weight; i++)
f[i] = max(f[i], f[i-weight]+cost)
}
初始化的細節(jié)問題
一般問題會有兩種問法:
- 剛好裝滿背包
- 不用裝滿背包
如果是第一種,f[0]=0,f[1]……f[N]=INF;
如果是第二種核芽,f[0]……f[N]=INF;
理解:
如果是第一種囚戚,初始狀態(tài)只有0符合理想狀態(tài),只有0才能被空“裝滿”轧简。
如果是第二種驰坊,所有都符合理想狀態(tài)。
完全背包問題
題意
和01背包相似哮独,所不同的是可取的物品數(shù)是無限拳芙。
前置小優(yōu)化
對于i``j
兩個物品,如果c[i]>c[j] && w[i]<w[j]
皮璧,就舍去i
物品舟扎。
另外,針對背包問題而言悴务,比較不錯的一種方法是:首先將重量大于V的物品去掉睹限,然后使用類似計數(shù)排序的做法,計算出費用相同的物品中價值最高的是哪個讯檐,可以O(V+N)地完成這個優(yōu)化羡疗。
基本思路
狀態(tài)轉(zhuǎn)移方程f[i][v]=max{f[i-1][v-k*w[i]]+k*c[i]},(0<=k*w[i]<=V)
轉(zhuǎn)化為01背包求解
一件物品最多只能放V/c[i]
件,所以可以把一件物品别洪,看成V/c[i]
件物品叨恨,作為01背包解答。
另一種更好的辦法是把第i
種物品拆成大小為w[i]*2^k
挖垛、價值為c[i]*2^k
的若干件物品痒钝,其中k
滿足w[i]*2^k<=V
。這是二進制的思想痢毒,因為不管最優(yōu)策略選幾件第i種物品送矩,總可以表示成若干個2^k件物品的和。這樣把每種物品拆成O(log(V/w[i]))
件物品闸准,是一個很大的改進益愈。
O(VN)
算法
for(int i=1; i<=N; i++)
for(int j=w[i]; j<=V; j++)
f[j] = max{f[v], f[v-w[i]]+c[i]};
這個算法和之前的01背包相比只是第二層的遍歷方向改變了。因為01背包要保證每個物品只能選擇一次夷家,但是完全背包不必蒸其,所以改變遍歷方向就可以得到結(jié)果。
這個算法也可以從另外的思路中得出库快,例如摸袁,基本思路中的公式可以化作這個形式:f[i][v]=max(f[i-1][v], f[i][v-w[i]]+c[i]);
用函數(shù)封裝:
void CompletePack(int cost, int weight){
for(int i=weight; i<=V; i++)
f[i] = max(f[i], f[i-weight]+cost);
}
多重背包問題
題意
每件物品數(shù)量不一定為1但有限。
基本思路
問題和完全背包很相似义屏。
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[I]}(0<=k<=n[I])
復雜度為O(V*Σn[i])
靠汁。
轉(zhuǎn)化為01背包問題
用n[i]
存儲蜂大,可以將每種物品轉(zhuǎn)化為n[i]
件物品,然后用01背包方案求解蝶怔。復雜度不變奶浦。
如果要進行優(yōu)化的話,依然用二進制思想踢星,同上澳叉。
這樣可以將時間優(yōu)化為O(V*Σlog n[i])
。
void MultiplePack(int weight, int cost, int amount){
if(cost * amount >= V){
CompletePack(cost, weight);
return;
}
int k = 1;
while(k < num){// num 為物品種數(shù)
ZeroOnePack(k*cost, k*weight);
amount = amount-k;
k *= 2;
}
ZeroOnePack(amount*cost, amount*weight);
}