01背包問題:POJ3624


背包問題是動態規划中的經典問題,而01背包問題是最基本的背包問題,也是最需要深刻理解的,否則何談復雜的背包問題。

POJ3624是一道純粹的01背包問題,在此,加入新的要求:輸出放入物品的方案。

我們的數組基於這樣一種假設:

totalN表示物品的種類,totalW表示背包的容量

w[i]表示第i件物品的重量,d[i]表示第i件物品的價值。

F(i,j)表示前i件物品放入容量為j的背包中,背包內物品的最大價值。

F(i,j) = max{ F(i-1,j) , F(i-1,j-w[i])+d[i] }

我們僅考慮第i件物品到底放不放進背包

第一項表示不放入背包的情況。

第二項表示放入背包的情況。

然后,我們為了獲得具體的方案,每當在局部最優的方案成立時,則將path[i][j]置為1,當求的最優結果后,從最終結果開始回溯,看看在第i輪的局部最優中是否放入物品i。

此時的代碼如下所示:

#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
using namespace std;
#define max(a,b) a>b?a:b;
//數組要設的比給的范圍稍大一些
int dp[3410][12900];
int path[3410][12900];
int w[3410];
int d[3410];
int totalN;
int totalW;
int main()
{
    int i,j;
    scanf("%d",&totalN);
    scanf("%d",&totalW);

    for(i=1;i<=totalN;i++)
    {
        scanf("%d",&w[i]);
        scanf("%d",&d[i]);
    }

    memset(dp,0,sizeof(dp));
    for(i=1;i<=totalN;i++){
        for(j=1;j<=totalW;j++){
            /*必不可少,需要根據上一輪狀態,
            而如果直接從j=w[i]開始,會使0<j<w[i]都為0,影響正確的結果。
            在之后優化的1維矩陣方法中,就不會存在這種問題,
            由於其逆序遍歷,上一輪的結果只要不覆蓋,就一直存在*/
            dp[i][j] = dp[i-1][j];
            if(j>=w[i] && dp[i][j]<dp[i-1][j-w[i]]+d[i]){
                dp[i][j] =dp[i-1][j-w[i]]+d[i];
                path[i][j] = 1;
            }
        }
    }
    printf("背包內物品的最大價值是:\n");
    printf("%d\n",dp[totalN][totalW]);
    i = totalN;
    j = totalW;
    printf("放入背包的物品是:\n");
    while(i>0 && j>0)
    {
        if(path[i][j] == 1)
        {
            printf("%d\n",d[i]);
            j = j - w[i];
        }
        i = i - 1;
    }

    system("pause");
    return 0;
}

此時的時間復雜度是O(NW),已經無法優化,但空間復雜度可以由O(NW)下降到O(W).。

我們可以看到F(i,j)僅僅依賴於F(i-1,j)和F(i-1,j-w[i])+d[i],也就是說,我們需要依賴的上一輪的序列中的元素的坐標沒有在J的右邊的,此時我們就可以使用逆序遍歷即可,直接應用上一輪的數據。

這樣的逆序遍歷還有一層意義,即每個物品只放入一次,因為所以來的兩項都是沒有放入過第i件物品的情況,從不依賴放入第i件物品的情況。這點需要好好的理解一下,有助於后續對完全背包問題的理解。

上代碼:

#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
using namespace std;
//數組要設的比給的范圍稍大一些
int bag[12900];
int path[3410][12900];
int w[3410];
int d[3410];
int totalN;
int totalW;
int main()
{
    int i,j;
    scanf("%d",&totalN);
    scanf("%d",&totalW);

    for(i=1;i<=totalN;i++)
    {
        scanf("%d",&w[i]);
        scanf("%d",&d[i]);
    }

    memset(bag,0,sizeof(bag));
    for(i=1;i<=totalN;i++){
        for(j=totalW;j>=w[i];j--){
            if(bag[j]<bag[j-w[i]]+d[i]){
                bag[j] = bag[j-w[i]]+d[i];
                path[i][j]=1;
            }
        }
    }
    printf("背包內物品的最大價值是:\n");
    printf("%d\n",bag[totalW]);
    i = totalN;
    j = totalW;
    printf("放入背包的物品是:");
    while(i>0 && j>0)
    {
        if(path[i][j] == 1)
        {
            printf("%d\n",d[i]);
            j = j - w[i];
        }
        i = i - 1;
    }

    system("pause");
    return 0;
}

最后如果想去AC的話,要采用第二種方案,降低空間復雜度,AC代碼(折疊):

#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
using namespace std;
//數組要設的比給的范圍稍大一些
int bag[12900];
int w[3410];
int d[3410];
int totalN;
int totalW;
int main()
{
    int i,j;
    scanf("%d",&totalN);
    scanf("%d",&totalW);

    for(i=1;i<=totalN;i++)
    {
        scanf("%d",&w[i]);
        scanf("%d",&d[i]);
    }
    memset(bag,0,sizeof(bag));
    for(i=1;i<=totalN;i++){
        for(j=totalW;j>=w[i];j--){
            if(bag[j]<bag[j-w[i]]+d[i]){
                bag[j] = bag[j-w[i]]+d[i];
            }
        }
    }    
    printf("%d\n",bag[totalW]);
    system("pause");
    return 0;
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM