1. 程式人生 > >DP揹包問題小結(01揹包,完全揹包,需恰好裝滿或不需,一維DP、二維DP)

DP揹包問題小結(01揹包,完全揹包,需恰好裝滿或不需,一維DP、二維DP)

1)
揹包基礎,先以01揹包、求揹包所裝物品價值之和的最大值、不要求恰好裝滿時,易於理解的二維DP陣列儲存為例:

#include <iostream>
#include <string.h>

using namespace std;
int dp[1010][1010];
int vp[1010];
int wp[1010];
int main()
{
    int kase;cin>>kase;
    while(kase--){
        memset(dp,0,sizeof(dp));
        int n,v;cin>>n>>v;
        int v1,w1;
        for(int i=1;i<=n;i++){
            cin>>vp[i];
        }
        for(int i=1;i<=n;i++){
            cin>>wp[i];
        }
        for(int i=1;i<=n;i++){
            v1=vp[i];w1=wp[i];
            for(int j=0;j<=v;j++){//正序、逆序,皆可,因為有第一唯i的存在,保證了每一次更新都保證不會放入相同的物品
                if(j>=w1)
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-w1]+v1);
                else
                    dp[i][j]=dp[i-1][j];//假如第i個因為體積原因放不進去,那麼d第i個的狀態繼承第i-1的狀態(如果不操作,則為初始值0)

            }
        }
        cout<<dp[n][v]<<endl;
    }
}



2)以01揹包,採用方便寫的一維DP陣列,求揹包所裝物品價值之和最大值,不要求恰好裝滿揹包為例:

//三個小分支:

//改為求完全揹包時(¥1處有變化)
//改為求揹包所裝物品價值之和的最小值時,(#1與#2處程式碼,有變化)
//改為要求恰好裝滿揹包時(@1處程式碼有變化)

#include <iostream>
#include <string.h>

using namespace std;
const int inf=100000000;//#1求揹包所裝物品價值之和的最大值時,inf為負無窮,如果求最小值,inf為正無窮,具體寫幾個0,看題目要求的限制
int main()
{
    int kase;cin>>kase;
    int value[1010];
    int volume[1010];
    int dp[1010];
    while(kase--){
        int n;cin>>n;
        int bag;cin>>bag;
        memset(dp,0,sizeof(dp));/*@1不要求恰好裝滿揹包時,則dp[0],和dp[1...n]都置為0。@2為替換程式碼。理由在@2下面。
	/*  //@2要求恰好裝滿揹包時,則dp[0]=0,dp[1...n]都為正無窮或者負無窮(取決於題目是求最大值還是最小值,看#1處)
	dp[0]=0;
	for(int i=1;i<=bag;i++)
		dp[i]=inf;
	*/
	/*
	當我們把1到n初始化為無窮值,只有0是初始化為0時,這時其他不變,一切更新照常,所不同的是,只有以0作為基礎的更新的值處於正常範圍,而其他值都因為初始無窮的原因,每一次更新後的數都大於等於正無窮(如果取最小值則每一次更新後還是正無窮,如果取最大值,沒一次更新都大於正無窮),那麼區分是否能恰好裝滿,只需要看最後一個狀態的值是不是正常範圍內的值即可。舉例如下:

f為負無窮,g為正無窮,sum為揹包總體積,v為物品價值,w為物品體積
sum:8
v w
3 2
4 3
w:0->n
要求恰好裝滿時,將0為0,將1,2,3...9都非0的初始值,又因為取最大值,所以非0的初始值是負無窮:
初始值: 0   1   2   3   4   5   6    7     8     9
         0   f   f   f   f   f   f    f     f     f
                    0+2 f+2 f+2 2+2 f+2+2 f+2+2  2+2+2
                        0+3 f+3 f+3  2+3   3+3   f+3+3
(
先放大數或者先放小數並沒有區別,舉例如下,先放v==4,w==3    
初始值: 0   1   2   3   4   5   6    7     8     9
         0   f   f   f   f   f   f    f     f     f
                        0+3 f+3 f+3  f+3   3+3  f+3+3
		    0+2 f+2 f+2 2+2 f+2+3 f+2+3 2+2+2  
)

不要求恰好裝滿時,此時0到9全部為0,不論取最大值還是取最小值,都是0做初始值:
初始值: 0   1   2   3   4   5   6    7     8     9
         0   0   0   0   0   0   0    0     0     0
                     2   2   2  2+2  2+2   2+2   2+2+2
	*/

        for(int i=0;i<n;i++){
            cin>>value[i];
        }
         for(int i=0;i<n;i++){
            cin>>volume[i];
         }
         for(int i=0;i<n;i++){
            for(int j=bag;j>=volume[i];j--){//*¥1,01揹包每一種物品只能放一次,則此處要j從大到小,採用逆序(以保證,每一次更新放入的物品都不重複,都是在上一個物品放置後的情況下更新的),而完全揹包,j從小到大,正序,這樣使一種物品可以重複放置。兩者區別具體講,當j逆序由大到小如由j==3到j==2變化時,假設volume[i]=1,j-volume[i]也是逐漸減小,依次對應為,2、1,那麼狀態轉移方程就是dp[3]=max(dp[3],dp[2]+value[i]);dp[2]=max(dp[2],dp[1]+value[i]);如果是完全揹包,j正序由小變大,那麼dp[2]=max(dp[2],dp[1]+value[i]);dp[3]=max(dp[3],dp[2]+value[i]);最後一個式子中,進行比較所用的dp[2]剛剛被更新過,是現在這個物品放置一次、更新後的情況而非上一個物品放置後的情況!所以使同一個物品可以重複放置!*/
                    dp[j]=(dp[j]>dp[j-volume[i]]+value[i])?dp[j]:dp[j-volume[i]]+value[i];//#2,此處取大的一方更新原來的數,如果求最小值自然取小的一方更新原來的數。例如下面
	/*
		dp[j]=(dp[j]<dp[j-volume[i]]+value[i])?dp[j]:dp[j-volume[i]]+value[i];//
	*/
    
            }
         }
         cout<<dp[bag]<<endl;
    }
}

3)練習題目

01揹包、不要求恰好裝滿、求最大值,入門題,hdu2602;

完全揹包、要求恰好裝滿、求最小值,入門題,hdu1114;

hdu2602 AC程式碼:

#include <iostream>
#include <string.h>
#include <map>

using namespace std;
//map <int,int> bone;
int main()
{
    int kase;cin>>kase;
    int value[1010];
    int volume[1010];
    int dp[1010];
    while(kase--){
        int n;cin>>n;
        int bag;cin>>bag;
        memset(dp,0,sizeof(dp));
        //int value,volume;
        for(int i=0;i<n;i++){
            cin>>value[i];
            //bone[value]=volume;
        }
         for(int i=0;i<n;i++){
            cin>>volume[i];
         }
         for(int i=0;i<n;i++){
            for(int j=bag;j>=0;j--){
                if(j>=volume[i]){
                    dp[j]=(dp[j]>dp[j-volume[i]]+value[i])?dp[j]:dp[j-volume[i]]+value[i];
                }
                //else
                    //dp[j]=dp[j];
            }
         }
         cout<<dp[bag]<<endl;
    }
}

hdu1114 AC程式碼:
#include <iostream>
#include <string.h>

using namespace std;
struct coin{
    int value;
    int weight;
}coins[510];
int dp[10010];

//求最小值,用正無窮:
long long int inf=100000000;

int main()
{
    int kase;cin>>kase;
    while(kase--){
        int emp,full;cin>>emp>>full;
        int volume=full-emp;
        int n;cin>>n;

        //恰好裝滿,不同初始值:
        dp[0]=0;
        for(int i=1;i<=volume;i++){
            dp[i]=inf;
        }

        for(int i=0;i<n;i++){
            cin>>coins[i].value;
            cin>>coins[i].weight;
        }

       //完全揹包,正序:
        for(int i=0;i<n;i++){
            for(int j=coins[i].weight;j<=volume;j++){
                dp[j]=min(dp[j],dp[j-coins[i].weight]+coins[i].value);
            }
        }
        if(dp[volume]==inf)
            cout<<"This is impossible."<<endl;
        else{
            printf("The minimum amount of money in the piggy-bank is %d.\n",dp[volume]);
        }
    }
}