揹包九講【ORZ式教學】
揹包問題
P01: 01揹包問題
1.1 問題
有N件物品和一個容量為V的揹包。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。
1.2 基本思路
在不超過揹包容量的情況下,最多能獲得多少價值
子問題狀態:f[j]:表示前i件物品放入容量為j的揹包得到的最大價值
狀態轉移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
初始化:f陣列初始狀態設定為0
1.3 程式碼部分
#include <bits/stdc++.h>
using namespace std;
const int N = 4;//物品個數
const int V = 5;//揹包最大容量
int weight[N] = {1,2,2,3};//物品重量
int value[N] = {25,10,20,30};//物品價值
int f[V + 1];
int ZeroOnePack()
{
memset(f,0,sizeof(f));
//遞推
for (int i = 0;i < N;i++) //列舉物品(注意物品的編號)
{
for (int j = V;j >= weight[i];j--) //列舉揹包容量,防越界,j下限為 weight[i]!!!
{
f[j] = max(f[j],f[j - weight[i]] + value[i]);
cout << j << " " << f[j] << endl;
}
}
return f[V];
}
int main()
{
cout << ZeroOnePack() << endl;
return 0;
}
1.4 初始化細節問題
我們看到的求最優解的揹包問題題目中,事實上有兩種不太相同的問法。
有的題目要求“恰好裝滿揹包”時的最優解,有的題目則並沒有要求必須把揹包裝滿。
如果是第一種問法,要求恰好裝滿揹包,那麼在初始化時除了F[0]為0,其它F[1..V ]均設為−∞,這樣就可以保證最終得到的F[V ]是一種恰好裝滿揹包的最優解。
如果並沒有要求必須把揹包裝滿,而是隻希望價格儘量大,初始化時應該將F[0..V ]全部設為0。 這是為什麼呢?可以這樣理解:初始化的F陣列事實上就是在沒有任何物品可以放入揹包時的合法狀態。如果要求揹包恰好裝滿,那麼此時只有容量為0的揹包可以在什麼也不裝且價值為0的情況下被“恰好裝滿”,其它容量的揹包均沒有合法的解,屬於未定義的狀態,應該被賦值為-∞了。
如果揹包並非必須被裝滿,那麼任何容量的揹包都有一個合法解“什麼都不裝”,這個解的
價值為0,所以初始時狀態的值也就全部為0了。
P02: 完全揹包問題
2.1 問題
有N種物品和一個容量為V 的揹包,每種物品都有無限件可用。放入第i種 物品的耗費的空間是Ci,得到的價值是Wi。求解:將哪些物品裝入揹包,可使 這些物品的耗費的空間總和不超過揹包容量,且價值總和最大。
2.2 基本思路
可以考慮將完全揹包轉換為01揹包問題來解。
在解決01揹包問題的過程中,如果揹包的容量倒著從V開始減,那麼就意味著,每個物品每次只能選擇一次,但是反過來就相當於每個物品只要體積之和不超過指定的V,就可以一直取下去,從而就可以看成每個物品都有無限多件。
2.3 程式碼部分
int CompletePack()
{
memset(f,0,sizeof(f));
//遞推
for (int i = 0;i <= N; ++ i) //列舉物品(注意物品的編號)
{
for (int j = weight[i];j <= V; ++ j) //列舉揹包容量,防越界,j下限為 weight[i]!!!
{
f[j] = max(f[j],f[j - weight[i]] + value[i]);
cout << j << " " << f[j] << endl;
}
}
return f[V];
}
2.4 一個簡單的優化
完全揹包問題有一個很簡單有效的優化,是這樣的:若兩件物品i、j滿足Ci ≤ Cj且Wi ≥ Wj,則將可以將物品j直接去掉,不用考慮。不過一般是用不到的。
P03: 多重揹包問題
3.1 問題
有N種物品和一個容量為V 的揹包。第i種物品最多有Mi件可用,每件耗費的空間是Ci,價值是Wi。求解將哪些物品裝入揹包可使這些物品的耗費的空間總和不超過揹包容量,且價值總和最大。
3.2 基本思路
把第i種物品換成Mi件01揹包中的物品,則得到了物品數為ΣMi的01揹包問題。
我們可以通過二進位制的拆分方法對其優化。對每i件物品,拆分的策略為:新拆分的物品的重量等於1件,2件,4件,..,(2^(k - 1)),Num[i] - (2^(k - 1))件,其中k 是滿足Num[i] - 2^k + 1 > 0 的最大整數。
注意:
(1)最後一個物品的件數的求法和前面不同,其直接等於 該物品的最大件數 - 前面已經分配之和。
(2)分成的這幾件物品的係數和為Num[i],表明第i種物品取的件數不能多於Num[i]。
舉例:某物品為13件,則其可以分成四件物品,其係數為1,2,4,6.這裡k = 3。
使用二進位制的前提還是使用二進位制拆分能保證對於0,,,Num[i]間的每一個整數,均可以用若干個係數的和表示。
3.3 程式碼部分
/*
01揹包,v為降序
f[v]:表示把前i件物品放入容量為v的揹包中獲得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void ZeroOnePack(int nWeight,int nValue)
{
for (int v = V; v >= nWeight; v--)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
/*
完全揹包,v為增序。
f[v]:表示把前i件物品放入容量為v的揹包中獲得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void CompletePack(int nWeight,int nValue)
{
for (int v = nWeight; v <= V; v++)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
int MultiplePack()
{
int k = 1;
int nCount = 0;
for (int i = 1; i <= N; i++)
{
if (weight[i] * num[i] >= V)
{
//此時滿足條件Weight[i] * Num[i] >= V時,
//完全揹包:該類物品相當於是無限供應,直到揹包放不下為止。
CompletePack(weight[i],value[i]);
}
else
{
k = 1;
nCount = num[i];
while(k <= nCount)
{
ZeroOnePack(k * weight[i],k * value[i]);
nCount -= k;
k *= 2;
}
ZeroOnePack(nCount * weight[i],nCount * value[i]);
}
}
return f[V];
}
以上的程式碼可直接拿做多重揹包的模板(記得初始化),但是重要的還是思想,注意靈活運用。
3.4 實戰部分
HDOJ 2192 悼念512汶川大地震遇難同胞——珍惜現在,感恩生活
題目概述:多重揹包模板題目
程式碼部分:
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 1005;
int f[maxn + 1];
int weight[maxn + 1], value[maxn + 1], num[maxn + 1];
int V,N;
/*
01揹包,v為降序
f[v]:表示把前i件物品放入容量為v的揹包中獲得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void ZeroOnePack(int nWeight,int nValue)
{
for (int v = V; v >= nWeight; v--)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
/*
完全揹包,v為增序。
f[v]:表示把前i件物品放入容量為v的揹包中獲得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void CompletePack(int nWeight,int nValue)
{
for (int v = nWeight; v <= V; v++)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
int MultiplePack()
{
int k = 1;
int nCount = 0;
for (int i = 1; i <= N; i++)
{
if (weight[i] * num[i] >= V)
{
//此時滿足條件Weight[i] * Num[i] >= V時,
//完全揹包:該類物品相當於是無限供應,直到揹包放不下為止。
CompletePack(weight[i],value[i]);
}
else
{
k = 1;
nCount = num[i];
while(k <= nCount)
{
ZeroOnePack(k * weight[i],k * value[i]);
nCount -= k;
k *= 2;
}
ZeroOnePack(nCount * weight[i],nCount * value[i]);
}
}
return f[V];
}
int main()
{
int t,i;
cin>>t;//會輸入幾組資料
while(t--)
{
memset(f, 0, sizeof(f));
cin>>V>>N;//v:揹包容量 n:物體個數
for(i = 1 ; i <= N; i++)//從i = 1開始輸入!所以最後輸出f[V]不是f[V-1]!
cin>>weight[i]>>value[i]>>num[i];
cout<<MultiplePack()<<endl;
}
return 1;
}
附加題: HDOJ 1059 Dividing
P04: 混合三種揹包問題
4.1 問題
如果將前面1、2、3中的三種揹包問題混合起來。也就是說,有的物品只可
以取一次(01揹包),有的物品可以取無限次(完全揹包),有的物品可以取
的次數有一個上限(多重揹包)。
4.2 01揹包與完全揹包的混合
考慮到01揹包和完全揹包中給出的虛擬碼只有一處不同,故如果只有兩類
物品:一類物品只能取一次,另一類物品可以取無限次,那麼只需在對每個
物品應用轉移方程時,根據物品的類別選用順序或逆序的迴圈即可,複雜度
是O(V N)。虛擬碼如下:
for i = 1 to N
if 第i件物品屬於01揹包
for v = V to Ci
F[v] = max(F[v], F[v − Ci] + Wi)
else if 第i件物品屬於完全揹包
for v = Ci to V
F[v] = max(F[v], F[v − Ci] + Wi)
4.3 三種揹包混合
for i = 1 to N
if 第i件物品屬於01揹包
ZeroOnePack(F,Ci ,Wi )
else if 第i件物品屬於完全揹包
CompletePack(F,Ci ,Wi )
else if 第i件物品屬於多重揹包
MultiplePack(F,Ci ,Wi ,Ni )
P05: 二維費用的揹包問題
5.1 問題
二維費用的揹包問題是指:對於每件物品,具有兩種不同的費用;選擇這件物品必須同時付出這兩種代價;對於每種代價都有一個可付出的最大值(揹包容量)。問怎樣選擇物品可以得到最大的價值。設這兩種代價分別為代價1和代價2,第i件物品所需的兩種代價分別為a[i]和b[i]。兩種代價可付出的最大值(兩種揹包容量)分別為V和U。物品的價值為w[i]。
5.2 基本思路
費用加了一維,只需狀態也加一維即可。設f[i][v][u]表示前i件物品付出兩種代價分別為v和u時可獲得的最大價值。狀態轉移方程就是:
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}
如前述方法,可以只使用二維的陣列:當每件物品只可以取一次時變數v和u採用逆序的迴圈,當物品有如完全揹包問題時採用順序的迴圈。當物品有如多重揹包問題時拆分物品。根據題目要求使用相應的揹包方法。
5.3 實戰部分
題目連結: HDOJ 2159 FATE
題目描述: 現在的問題是,xhd升掉最後一級還需n的經驗值,xhd還留有m的忍耐度,每殺一個怪xhd會得到相應的經驗,並減掉相應的忍耐度。當忍耐度降到0或者0以下時,xhd就不會玩這遊戲。xhd還說了他最多隻殺s只怪。請問他能升掉這最後一級嗎?
樣例輸入: 輸入資料有多組,對於每組資料第一行輸入n,m,k,s(0 < n,m,k,s < 100)四個正整數。分別表示還需的經驗值,保留的忍耐度,怪的種數和最多的殺怪數。接下來輸入k行資料。每行資料輸入兩個正整數a,b(0 < a,b < 20);分別表示殺掉一隻這種怪xhd會得到的經驗值和會減掉的忍耐度。(每種怪都有無數個)
樣例輸出: 輸出升完這級還能保留的最大忍耐度,如果無法升完這級輸出-1。
測試資料:
Sample Input
10 10 1 10
1 1
10 10 1 9
1 1
9 10 2 10
1 1
2 2
Sample Output
0
-1
1
解題思路:從題目可以看出,怪物可以無限刷,那麼這就是完全揹包問題。因為題目中涉及到兩個條件(忍耐度,刷怪數量),可以看出這是一個二維費用完全揹包題目。
我們應該用一個二維的dp陣列存放在一定的(刷怪數量和和忍耐度)的狀態,得到的經驗是多少。
狀態轉移方程:dp[j][z] = max(dp[j - 1][z - b[i]] + a[i], dp[j][z]);
(j為刷怪數量,z為忍耐度,b[i]為第i個怪消耗掉的忍耐度,a[i]代表第i個怪的得到的經驗)。最後注意是完全揹包就可以了。
程式碼部分:
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 1e2 + 10;
int dp[maxn][maxn];
int a[maxn], b[maxn];
int main()
{
int n, m, k, s;
while(~scanf("%d%d%d%d", &n, &m, &k, &s))
{
for(int i = 1; i <= k; ++ i)
{
scanf("%d%d", &a[i], &b[i]);
}
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= k; ++ i)//怪的種類
{
for(int z = b[i]; z <= m; ++ z)//消耗的忍耐(完全揹包)
{
for(int j = 1; j <= s; j ++) //打怪的數量 必須從1到s
{
dp[j][z] = max(dp[j - 1][z - b[i]] + a[i], dp[j][z]);
}
}
}
if(dp[s][m] >= n)
{
for(int i = 0; i <= m; ++ i)
{
if(dp[s][i] >= n)
{
printf("%d\n", m - i);
break;
}
}
}
else
{
printf("-1\n");
}
}
return 0;
}
P06: 分組揹包問題
6.1 問題
有N件物品和一個容量為V的揹包。第i件物品的費用是c[i],價值是w[i]。這些物品被劃分為若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。
6.2 基本思路
這個問題變成了每組物品有若干種策略:是選擇本組的某一件,還是一件都不選。也就是說設f[k][v]表示前k組物品花費費用v能取得的最大權值,則有f[k][v] = max{ f[k-1][v], f[k-1][v-c[i]]+w[i] | 物品i屬於第k組 }。
6.3 實戰部分
HDOJ 3535 AreYouBusy
題目描述:有n個組的工作,有t的時間去做
對於組有三種類型
2隨意,拿或者不拿,選其中多少都可以
1最多選一個
0至少選一個
每組每件事有花費的時間和收益
取儘量高的收益
解題思路:
經典混合揹包
題目給了很多類別的物品。用 陣列dp[i][j],表示第i組,時間為j時的快樂值。每得到一組工作就進行一次DP,所以dp[i]為第i組的結果。
第一類(01揹包坑點多),至少選一項,即必須要選,那麼在開始時,對於這一組的dp的初值,應該全部賦為負無窮,這樣才能保證不會出現都不選的情況。
狀態轉移方程:dp[i][j]=max(dp[i][j],max(dp[i][j-w[x]]+p[x],dp[i-1][j-w[x]]+p[x]));
dp[i][j]: 是不選擇當前工作;
dp[i-1][j-w[x]]+p[x]: 第一次在本組中選物品,由於開始將該組dp賦為了負無窮,所以第一次取時,必須由上一組的結果推知,這樣才能保證得到全域性最優解;
dp[i][j-w[x]]+p[x]:表示選擇當前工作,並且不是第一次取;
第二類(分組揹包),最多選一項,即要麼不選,一旦選,只能是第一次選。
狀態轉移方程:dp[i][j]=max(dp[i][j],dp[i-1][j-w[x]]+p[x]);
由於要保證得到全域性最優解,所以在該組DP開始以前,應該將上一組的DP結果先複製到這一組的dp[i]數組裡,因為當前組的資料是在上一組資料的基礎上進行更新的。
第三類(01揹包),任意選,即不論選不選,選幾個都可以。
狀態轉移方程為:dp[i][j]=max(dp[i][j],dp[i][j-w[x]]+p[x]);
同樣要保證為得到全域性最優解,先複製上一組解,資料在當前組更新。
程式碼部分:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,sum;
int w[110],p[110];
int dp[110][110];
int main()
{
while(~scanf("%d%d",&n,&sum))
{
memset(dp,0,sizeof(dp));
int i,j,k,g;
for(i=1; i<=n; i++)
{
scanf("%d%d",&m,&g);
for(k=1; k<=m; k++)
scanf("%d%d",&w[k],&p[k]);
if(g==0)
{
for(j=0; j<=sum; j++) //當前組初始化
dp[i][j]=-INF;
for(k=1; k<=m; k++)
for(j=sum; j>=w[k]; j--)
dp[i][j]=max(dp[i][j],max(dp[i][j-w[k]]+p[k],dp[i-1][j-w[k]]+p[k]));
}
else if(g==1)
{
for(j=0; j<=sum; j++) //當前組初始化
dp[i][j]=dp[i-1][j];
for(k=1; k<=m; k++)
for(j=sum; j>=w[k]; j--)
dp[i][j]=max(dp[i][j],dp[i-1][j-w[k]]+p[k]);
}
else if(g==2)
{
for(j=0; j<=sum; j++) //當前組初始化
dp[i][j]=dp[i-1][j];
for(k=1; k<=m; k++)
for(j=sum; j>=w[k]; j--)
dp[i][j]=max(dp[i][j],dp[i][j-w[k]]+p[k]);
}
}
dp[n][sum]=max(dp[n][sum],-1); //沒有完成任務的值都為負的,做輸出調整,輸出-1
printf("%d\n",dp[n][sum]);
}
return 0;
}
P07: 有依賴揹包問題
7.1 問題
這種揹包問題的物品間存在某種“依賴”的關係。也就是說,i依賴於j,表示若選物品i,則必須選物品j,問選擇n件物品,可以得到的最大價值。舉個例子,在大學中,要修很多功課,但是這些功課有的都有先行課,比如在學C++ 之前,必須要學C++,這時候C++ 就依賴於C,當C沒有學習的時候,就沒有辦法去學習C++。
7.2 基本思路
首先先簡化一下問題:先設沒有某個物品既依賴於別的物品,又被別的物品所依賴;另外,沒有某件物品同時依賴多件物品。
這時候我們可以將被依賴的物品稱為“主件”,依賴主件的物品稱為“附件”。
首先先分析一下物品可以怎麼選擇:對於每個主件和其對應的附件,主件可以分為選與不選。附件可以選一個,兩個……普通的揹包是無法解決這樣的問題。
解決問題方法:我們可以先將主件對應的附件做一個01 揹包的處理。這時候對於每個主件就可以轉換成分組揹包的問題,每組中包含(不選主件,選主件不選附件,選主件選一個附件,選主件選兩個附件……)。
一般問題方案:依賴關係以圖論中“森林”的形式給出(森林即多叉樹的集合),也就是說,主件的附件仍然可以具有自己的附件集合,限制只是每個物品最多隻依賴於一個物品(只有一個主件)且不出現迴圈依賴。
事實上,這是一種樹形DP,其特點是每個父節點都需要對它的各個兒子的屬性進行一次DP以求得自己的相關屬性。
7.3 實戰部分
HDOJ 3449 Consumer
題目意思:有N個箱子,每個箱子需要花費Ai的代價
箱子裡面有K個物品,K個物品你可以選擇買任意個,但每個物品只能買一個,每個物品有相應的花費和價值
你又M的大洋,最後問最多能得到多少價值物品。
解題思路:先對每組選擇裡的所有物品進行0-1揹包處理,但揹包容量為(總容量-盒子容量);然後跟上一組的狀態比較來決定這一組選擇 是選還是不選,取其中的較大值。
程式碼部分:
#include<stdio.h>
#include<string.h>
const int maxn=100010;
int dp[maxn];
int tmp[maxn];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n,sum,p,m,w,v,i,j,k;
while(scanf("%d%d",&n,&sum)!=EOF)
{
memset(dp,0,sizeof(dp));
for(i=0; i<n; i++)
{
scanf("%d%d",&p,&m);
memcpy(tmp,dp,sizeof(dp));//繼承前面的
for(j=0; j<m; j++)
{
scanf("%d%d",&w,&v);
for(k=sum-p; k>=w; k--) //先將附件進行1次01揹包
tmp[k]=max(tmp[k],tmp[k-w]+v);
}
for(j=p; j<=sum; j++) //更新能更新的
dp[j]=max(dp[j],tmp[j-p]); //分組揹包
}
printf("%d\n",dp[sum]);
}
return 0;
}
HDOJ 1561 The more, The Better
題目意思: ACboy很喜歡玩一種戰略遊戲,在一個地圖上,有N座城堡,每座城堡都有一定的寶物,在每次遊戲中ACboy允許攻克M個城堡並獲得裡面的寶物。但由於地理位置原因,有些城堡不能直接攻克,要攻克這些城堡必須先攻克其他某一個特定的城堡。你能幫ACboy算出要獲得儘量多的寶物應該攻克哪M個城堡嗎?
每個測試例項首先包括2個整數,N,M.(1 <= M <= N <= 200);在接下來的N行裡,每行包括2個整數,a,b. 在第 i 行,a 代表要攻克第 i 個城堡必須先攻克第 a 個城堡,如果 a = 0 則代表可以直接攻克第 i 個城堡。b 代表第 i 個城堡的寶物數量, b >= 0。
解題思路:(有依賴揹包 || 樹形DP)
定義狀態dp[i][j] : 當前i節點及其子樹下最多選擇j個城市的最大值為dp[i][j];
我們考慮到特殊狀態:i節點下沒有孩子那麼dp[i][2,3,4,5…]均為-1(因為多選總比少選好,並且選擇完後城市總是有剩餘)
- 判斷當前節點P有沒有孩子,如果有則令當前節點為P重複(1)操作,如果沒有則到(2)操作;
- 將當前節點P的狀態更新到期父節點上,
更新操作為dp[P'father][i] = max(dp[P'father][i], dp[P'father][j]+dp[P][k])
(j + k = i ,j>0,k>0,2<=i<=max_cost,對於每一個i遍歷每一種(j,k)組合)
這裡的dp[P’father][j] j個城市一定是沒有包括P城市的其他j個城市的最大值
直到遍歷到root節點即可(dp[0][i])
3.輸出dp[0][max_cost]
max_cost 為題目中所給出的最多取幾個城市
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
程式碼部分:
#include <bits/stdc++.h>
using namespace std;
const int maxn=210;
vector<int>g[maxn];
int dp[maxn][maxn],v[maxn];
void dfs(int n,int m)
{
int siz=(int)g[n].size();
dp[n][1]=v[n];
for(int i=0; i<siz; i++)
{
int v=g[n][i];
if(m>=1) dfs(v,m-1);//遞迴先對子樹處理
for(int j=m; j>=1; j--)
{
for(int k=1; k<=j; k++)
{
dp[n][j+1] = max(dp[n][j+1],dp[n][j+1-k]+dp[v][k]);
}
}
}
}
int main()
{
int n,m,x;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0) break;
m++;
for(int i=0; i<=n; i++) g[i].clear();
memset(dp,0,sizeof(dp));
memset(v,0,sizeof(v));
for(int i=1; i<=n; i++)
{
scanf("%d%d",&x,&v[i]);
g[x].push_back(i);
}
dfs(0,m);
cout<<dp[0][m]<<endl;
}
return 0;
}
P08: 泛化揹包問題
8.1 問題
考慮這樣一種物品,它並沒有固定的費用和價值,而是它的價值隨著你分配給它的費用而變化。這就是泛化物品的概念。在揹包容量為V的揹包問題中,泛化物品是一個定義域為0..V中的整數的函式h,當分配給它的費用為v時,能得到的價值就是h(v)。
8.2 基本思路
如果面對兩個泛化物品h和l,要用給定的費用從這兩個泛化物品中得到最大的價值,怎麼求呢?事實上,對於一個給定的費用v,只需列舉將這個費用如何分配給兩個泛化物品就可以了。同樣的,對於0..V的每一個整數v,可以求得費用v分配到h和l中的最大價值f(v)。也即f(v)=max{h(k)+l(v-k)|0<=k<=v}。
這個運算的時間複雜度取決於揹包的容量,是O(V^2)。
P09:揹包問題變化
9.1 揹包的第K優解問題
問題描述 DD 和好朋友們要去爬山啦!他們一共有 K 個人,每個人都會背一個包。這些包的容量是相同的,都是 V。可以裝進揹包裡的一共有 N 種物品,每種物品都有給定的體積和價值。
在 DD 看來,合理的揹包安排方案是這樣的:
每個人揹包裡裝的物品的總體積恰等於包的容量。
每個包裡的每種物品最多隻有一件,但兩個不同的包中可以存在相同的物品。
任意兩個人,他們包裡的物品清單不能完全相同。
在滿足以上要求的前提下,所有包裡的所有物品的總價值最大是多少呢?
解題思路:用q1和q2記錄,保持遞增,合併成前k優解。 要使得揹包恰好裝滿,就要賦初值-maxint,而當揹包容量為0時,賦值0。這題和典型的01揹包求最優解不同,是要求第k大的解,所以,最直觀的想法就是在01揹包的基礎上再增加一維,用來儲存前k大小的數,然後在遞推時,根據前一個狀態的前k大小的數推出下一個階段的前k個數儲存下來。
程式碼部分:
#include<iostream>
using namespace std;
long long dp[51][50001];
int q1[51];
int q2[51];
int main()
{
int k,v,n;
int value,weight;
cin>>k>>v>>n;
for(int i=0; i<=k; i++)
for(int j=0; j<=v; j++)
dp[i][j]=INT_MIN;
dp[1][0]=0;
for(int i=1; i<=n; i++)
{
cin>>weight>>value;
for(int j=v; j>=weight; j--)
{
for(int w=1; w<=k; w++)
{
q1[w]=dp[w][j];
q2[w]=dp[w][j-weight]+value;
}
int h1=1,h2=1,h=0;
while(h<k)
{
h++;
if(q1[h1]>q2[h2])
{
dp[h][j]=q1[h1];
h1++;
}
else
{
dp[h][j]=q2[h2];
h2++;
}
}
}
}
int ans=0;
for(int i=1; i<=k; i++)
ans+=dp[i][v];
cout<<ans<<endl;
return 0;
}