DP背包问题小结(01背包,完全背包,需恰好装满或不需,一维DP二维DP)

Posted 嚜寒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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]);
        
    

以上是关于DP背包问题小结(01背包,完全背包,需恰好装满或不需,一维DP二维DP)的主要内容,如果未能解决你的问题,请参考以下文章

ACwing(基础)--- 01背包和完全背包多重背包问题

题解报告:NYOJ #311完全背包(恰好装满)

dp背包

hdu3496 二维01背包

nyoj 311-完全背包 (动态规划, 完全背包)

01背包