1. 程式人生 > >【海亮集訓DAY12(三)】對揹包的補充二的補充——第k優解

【海亮集訓DAY12(三)】對揹包的補充二的補充——第k優解


專輯:海量集訓-動態規劃-揹包


分三次更新


對第三次更新的補充


對於上次的更新,由於我還沒將第k優解的演算法研究透,所以就暫時沒有寫,在這裡不上


揹包的第k優解

第k優解,顧名思義,就是讓我們求出一個揹包問題的第k優解(第k優解是什麼想必不需要解釋了吧)

分析:如果相應的最優解問題能寫出狀態轉移方程、用動態規劃解決,那麼第K 優解則比求最優解的複雜度上多一個係數K 即可。
那我們還是以01揹包為例,狀態轉移方程為:

f[i−1][j−w[i]]+c[i], ②"> f [ i ] [ j ] =
m a x { f [ i 1 ]
[ j ]
 ①
f [ i 1 ] [ j w [ i ] ] + c [ i ] ,  ②

如果要求第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;
}