【海亮集訓DAY12(三)】對揹包的補充二的補充——第k優解
專輯:海量集訓-動態規劃-揹包
分三次更新
對第三次更新的補充
對於上次的更新,由於我還沒將第k優解的演算法研究透,所以就暫時沒有寫,在這裡不上
揹包的第k優解
第k優解,顧名思義,就是讓我們求出一個揹包問題的第k優解(第k優解是什麼想必不需要解釋了吧)
分析:如果相應的最優解問題能寫出狀態轉移方程、用動態規劃解決,那麼第K 優解則比求最優解的複雜度上多一個係數K 即可。
那我們還是以01揹包為例,狀態轉移方程為:
如果要求第k優解,那麼狀態f[i][c]就應該是一個大小為k的陣列f[i][c][k+1]。其中f[i][c][k]表示前i個物品、揹包大小為c時,第k優解的值。顯然可以認為f[i][c][k+1]是一個有序佇列。
然後可以說:f[i][c]這個有序佇列是有①、②這兩個序列合併得到的。有序佇列①即f[i-1][c][k],②則理解為在f[i-1][c-w[i]][k]的每個數上加上v[i]後得到的有序佇列。合併這兩個佇列並將結果前k項存到f[i][c][k+1]中的時間複雜度是O(K)。最後的答案是f[n][c][k]。總時間複雜度是O(cnk)。
實際上,一個正確的狀態轉移方程求解過程已經覆蓋了問題的所有方案。只不過由於是求最優解,所以其他達不到的最優的方案都被忽略了。因此,上面的做法是正確的。
另外,要注意題目對“第K優解”的定義,將策略不同但權值相同的兩個方案是看作是同一個解還是不同的解。如果是前者,則維護有序佇列是要保證佇列裡的數沒有重複的。
/*a陣列表示取取當前物品的第k優解,b陣列表示不取當前物品的第k優解
dp[0][1]=0;//初值
for (int i=1;i<=n;i++){//列舉物品
for (int j=v;j>=c[i];j--){//體積
int l=1,r=1;//l為a陣列的指標,r為b陣列的指標
for (int p=1;p<=k;p++){//列舉k種解
a[p]=dp[j-c[i]][p]+w[i]; //取
b[p]=dp[j][p];//不取
if (a[l]>=b[r]) dp[j][p]=a[l++];
else dp[j][p]=b[r++];//去一個較大值
}
}
}
第k優解的程式碼不是很長,但主要是理解,只要理解透,程式碼實現也不是那麼難。
那麼接下來就來一道是模板題的模板題
題目描述:DD 和好朋友們要去爬山啦!他們一共有 K 個人,每個人都會背一個包。這些包的容量是相同的,都是 V。可以裝進揹包裡的一共有 N 種物品,每種物品都有給定的體積和價值。
在 DD 看來,合理的揹包安排方案是這樣的:
1.每個人揹包裡裝的物品的總體積恰等於包的容量。
2.每個包裡的每種物品最多隻有一件,但兩個不同的包中可以存在相同的物品。
3.任意兩個人,他們包裡的物品清單不能完全相同。
在滿足以上要求的前提下,所有包裡的所有物品的總價值最大是多少呢?
分析:裸的第k優解的例題,套模板上去即可
不過不同的是,這道題並不是單純的求第k優解,而是求前k優解的和,只需要在最後套一重迴圈累加即可
具體程式碼如下:
#include<bits/stdc++.h>
using namespace std;
int n,v,k;
int a[100001]={};//不取
int b[100001]={};//取
int dp[5001][5001]={};//dp[i][j]表示填滿i的第j優解
int w[10001]={};//價值
int c[10001]={};//體積
int main(){
scanf("%d %d %d",&k,&v,&n);
for (int i=1;i<=n;i++) scanf("%d%d",&c[i],&w[i]);
memset(dp,-10,sizeof(dp));
dp[0][1]=0;
for (int i=1;i<=n;i++){
for (int j=v;j>=c[i];j--){
int l=1,r=1;
for (int p=1;p<=k;p++){
a[p]=dp[j-c[i]][p]+w[i];
b[p]=dp[j][p];
if (a[l]>=b[r]) dp[j][p]=a[l++];
else dp[j][p]=b[r++];
}
}
}//模板不解釋
int ans=0;
for (int i=1;i<=k;i++) ans+=dp[v][i];//累加前k優解的和
cout<<ans;
return 0;
}