背包问题 DP

 

 

01背包

 现在有1个体积为mmax的背包和n种物品(每种物品只有1个)。每种物品的体积和价值分别是v[i]和w[i]。求这个背包最多可以装价值多少的物品。

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:设f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。

我们依次遍历,到了第i件物品了

如果此时背包剩余体积已经放不下这件物品了,那么f[i][j]=f[i-1][j];

如果还能放的下这件物品我们就要考虑,到底是放入还是不放入这件物品会差生最大价值呢,我们需要做一个比较,也就是

f[i][j]=max(f[i-1][j],f[i-1][]j-v[i]+w[i]);

解释一下f[i-1][j]是不放入这件物品,f[i-1][]j-v[i]+w[i]是放入这件物品(这件物品的价值+前i-1件,体积减少了这件物品的背包中的价值)

模板如下

#define N 10010
int v[N],w[N];         //每件物品的体积和价值
int n,mmax;            //物品种类数 以及 背包体积
int f[N][N];           //dp数组

for(int i=1;i<=n;i++) cin>>v[i]>>w[i];  //提倡从1开始输入

for(int i=1;i<=n;i++)
    for(int j=0;j<=mmax;j++)
    {
        if(j<v[i]) f[i][j]=f[i-1][j];
        else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
    }

cout<<f[n][mmax];

可以实现空间优化,采用一维数组,模板如下

#define N 10010
int v[N],w[N];         //每件物品的体积和价值
int n,mmax;            //物品种类数 以及 背包体积
int f[N];              //dp数组

for(int i=1;i<=n;i++) cin>>v[i]>>w[i];  //提倡从1开始输入

for(int i=1;i<=n;i++)
    for(int j=mmax;j>=0;j--)
    {
        if(f[j]<=f[j-v[i]]+w[i] && j-v[i]>=0)
            f[j]=max(f[j],f[j-v[i]+w[i]);
    }

cout<<f[mmax];
 

完全背包

 现在有1个体积为mmax的背包和n种物品(每种物品有无限个)。每种物品的体积和价值分别是v[i]和w[i]。求这个背包最多可以装价值多少的物品。

这是最基础的背包问题,特点是:每种物品有无限个,可以选择放叶可以选择不放,选择放的时候可以放多个。

用子问题定义状态:设f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。

我们依次遍历,到了第i件物品了

f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i])     0<=k*v[i]<=j

模板如下

#define N 10010
int v[N],w[N];         //每件物品的体积和价值
int n,mmax;            //物品种类数 以及 背包体积
int f[N][N];           //dp数组

for(int i=1;i<=n;i++) cin>>v[i]>>w[i];  //提倡从1开始输入

for(int i=1;i<=n;i++)
    for(int j=0;j<=mmax;j++)
        for(int k=0;k*v[i]<=j;k++)
        {
            f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i])  
        }



cout<<f[n][mmax];

通常我们不用上述模板吗,而是采用优化后的模板,如下

优化思路: 转化为01背包问题求解

既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选mmax/v[i]件,于是可以把第i种物品转化为mmax/v[i]件体积及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。

更高效的转化方法是:把第i种物品拆成体积为v[i]*2^k、价值为w[i]*2^k的若干件物品,其中k满足v[i]*2^k<=mmax。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log(mmax/v[i]))件物品,是一个很大的改进。

代码:

#define N 10010
int v[N],w[N];         //每件物品的体积和价值
int n,mmax;            //物品种类数 以及 背包体积
int f[N];           //dp数组

for(int i=1;i<=n;i++) cin>>v[i]>>w[i];  //提倡从1开始输入

for(int i=1;i<=n;i++) 
    for(int j=v[i];j<=mmax;j++) 
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    
cout<<f[mmax];

多重背包

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。

状态转移方程:f[i][j]=max{f[i-1][j-k*v[i]]+k*w[i]}   0<=k<=n[i]

模板如下

#define N 10010
int v[N],w[N],num[N];       	//每件物品的体积和价值以及数量 
int n,mmax;            			//物品种类数 以及 背包体积
int f[N];           			//dp数组

for(int i=1;i<=n;i++) cin>>v[i] >>w[i]>>num[i];
for(int i=1;i<=n;i++)
    for(int j=mmax;j>=0;j--)
        for(int k=0;k<=n[i];k++)
        {
            if(j-k*v[i]<0) break;
            f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
        }
cout<<f[mmax];

 

优化后模板

#define N 10010
int v[N],w[N],num[N];       	//每件物品的体积和价值以及数量 
int n,mmax;            			//物品种类数 以及 背包体积
int f[N];           			//dp数组

for(int i=1;i<=n;i++) cin>>v[i] >>w[i]>>num[i];
int k = n + 1;
for(int i=1;i<=n;i++) 
{
    while(num[i]!=1) 
	{
        v[k]=v[i];
        w[k]=w[i];
        k++;
        num[i]--;
    }
}
for(int i=1;i<=k;i++) 
    for(int j=m; j>=1;j--) 
        if (v[i]<=j) f[j]=max(f[j],f[j-v[i]]+w[i]);
    
cout<<f[mmax];

 

 

 

 

 

部分参考自

;