背包问题

438 阅读2分钟

背包问题是经典的DP问题。

背包可分为下面几类

  1. 01背包
  2. 完全背包
  3. 多重背包
  4. 分组背包

01背包问题

问题

有n个物品,每个物品都有体积v和价值w。还有一个背包的体积为V,问背包能装的最大价值是多少。

分析问题

状态表示:dp[i][j]:前i个物品放进体积为j的背包中,背包中的最大价值

状态计算:主要就是考虑当前第i个物品选还是不选。
如果当前背包容量>当前物品的体积是可选的:v[i]>=j
选:dp[i][j]=dp[i-1][j-v[i]]+w[i];
不选:dp[i][j]=dp[i-1][j];

由于取的是最大值,所以在两者之前取max就可以了。Dp[n][V]就是答案;

代码

//01背包
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int dp[N][N];
int v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            
            if(v[i]<=j)//可选时取选与不选之间最大值。 
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            else //不可选 
                dp[i][j]=dp[i-1][j];
        }
    }
    cout<<dp[n][m]<<endl;
    return 0;
} 

我们可以发现此代码还可以继续优化,我们每次进行状态计算的时候,使用的是当前位置上面一行的当前列之前的数据。所以可以优化为一维数组进行滚动。

//01背包使用一维数组优化
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int dp[N];
int v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=1;j--)//由于需要使用上一行前j列的数据,
        {//所以需要从后往前遍历,避免更改需要使用的数据  
            if(v[i]<=j)
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m]<<endl;
    return 0;
} 

完全背包

问题

有n个物品,每个物品都有体积v和价值w。还有一个背包的体积为V,每个物品可以使用无限次,问背包能装的最大价值是多少。

分析问题

状态表示:dp[i][j]:前i个物品放入多个放进体积为j的背包中,背包中的最大价值

状态计算:由于每个物品可以选无限次,所以需要划分集合
(第i个物品选0个,1个,2个……k个);然后取最大值就行
Dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-kv[i]]+kw[i]);
但是这样需要3层循环,算法效率不高,我们需要继续优化;
Dp[i][j-v[i]]=max(dp[i-1][j-v[i]]+w[i],dp[i-1][j-kv[i]]+kw[i]);

合并之后:dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]);

代码

//完全背包
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int dp[N][N];
int v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(v[i]<=j)
                dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
            
        }
    }
    cout<<dp[n][m]<<endl;
    return 0;
}  

多重背包

问题

有n个物品,每个物品都有体积v和价值w。还有一个背包的体积为V,每个物品有s[i]个,问背包能装的最大价值是多少。

分析问题:

多重背包与完全背包比较类似,只是限制了每个物品的个数

状态表示:dp[i][j]:前i个物品放入多个放进体积为j的背包中,背包中的最大价值

状态计算:由于每个物品可以选无限次,所以需要划分集合
(第i个物品选0个,1个,2个……k个);然后取最大值就行k=min(j/v[i],s[i]);
但是由于可使用的是有限个,不可以像完全背包那样优化。

这里我们可以使用二进制来解决(二进制永远的神yyds),比如某个物品有11个:11(1011)=7(0111)+4(0100);就是将11分为比11小的数二进制末尾全是1的最大的的数+剩余的数,然后可以将11个物品分为4堆:1(0001),2(0010),4(0100),4(0100),这4堆物品可以分别选和不选然后可以结合成 0-11个物品,分完之后每推最多只能用一次,将这4推放物品集合中,有变成了01背包问题。

代码:

//多重背包问题
#include<bits/stdc++.h>
using namespace std;
const int N = 2005*11;
int dp[N];
int v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        while(k<=s)//将s分解然后装进物品集合中 例如11(1011)分为0001,0010,0100; 
        {
            v[++cnt]=a*k;
            w[cnt]=b*k;
            s-=k;
            k<<=1;
        }
        if(s>=0)//最后剩下11(1011)-7(0111)=4 
        {
            v[++cnt]=a*s;
            w[cnt]=b*s;
        }
        ;
    }
    for(int i=1;i<=cnt;i++)//对物品集合做01背包 
    {
        for(int j=m;j>=1;j--)
        {
            
            if(v[i]<=j) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
            
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}  

 

分组背包问题:

问题

有n组物品,每个物品都有体积v和价值w。还有一个背包的体积为V,每组最多只能选一个物品,问背包能装的最大价值是多少。

分析问题

状态表示:dp[i][j]:前i组物品放进体积为j的背包中,背包中的最大价值

状态计算:由于每组物品只能使用一个。需要划分集合
当前组(选0个、选第1个、第2个、第3个……第k个);
Dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i][1]]+w[i][1],……,dp[i-1][j-v[i][k]]+w[i][k])

代码:

//分组背包
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int dp[N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> s[i];
        for (int j = 1; j <= s[i]; j ++ )
            cin >> v[i][j] >> w[i][j];
    }
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 0; j -- )
            for (int k = 1; k <= s[i]; k ++ )
                if (v[i][k] <= j)
                    dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
    cout << dp[m] << endl;
    return 0;
}