背包问题汇总(详细证明+实现)

12 篇文章 0 订阅

背包问题汇总

0-1背包

题目:https://www.acwing.com/problem/content/2/
分析:

d p [ i ] [ j ] 意 思 是 只 放 前 i 个 物 品 在 总 体 积 是 j 的 情 况 下 的 物 品 的 贡 献 dp[i][j]意思是只放前i个物品在总体积是j的情况下的物品的贡献 dp[i][j]ij

易得状态方程为:

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] + w [ i ] ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]+w[i]]) dp[i][j]=max(dp[i1][j],dp[i1][jv[i]+w[i]])

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] 意 思 是 不 选 第 i 个 物 品 ( d p [ i ] [ j ] 直 接 原 封 不 动 保 留 上 一 个 i 的 状 态 ) dp[i][j]=dp[i-1][j]意思是不选第i个物品(dp[i][j]直接原封不动保留上一个i的状态) dp[i][j]=dp[i1][j]idp[i][j]i

代码如下:
#include <iostream>
#include <vector>
#include <cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m,v[1005],w[1005],dp[1005][1005];
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 =0; j <=m ; ++j) {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i]) //要当前背包的总体积大于v[i]才能把i物品放进背包
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
    }
//    for (int i = 1; i <=n ; ++i) {
//        for (int j = 0; j <=m ; ++j) {
//            cout<<dp[i][j]<<' ';
//        }
//        cout<<endl;
//    }
    int res=0;
    for (int i = 0; i <=m ; ++i) {
        res=max(res,dp[n][i]); //dp[n]意思是将n个物品都权衡利弊后选择n个物品中的部分放入背包(不超过背包最大容量),
    }													 //但不知道这些物品的总体积是多少,所以遍历所有可能的体积输出价值最大值即为答案。
    cout<<res<<endl;

}

测试数据:

4 5
1 2
2 4
3 4
4 5

dp数组内容:

0 2 2 2 2 2
0 2 4 6 6 6
0 2 4 6 6 8
0 2 4 6 6 8

最后输出:8
优化后的代码:

只用了一维

#include <iostream>
#include <vector>
#include <cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m,v[1005],w[1005],dp[1005];
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 >=v[i] ; --j) { //j从大到小循环是为了让dp[j-v[i]]的值是上一层i的值
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);//(dp[j-v[i]]后于dp[j]更新)
            //cout<<dp[j]<<' ';
        }
        //cout<<endl;
    }
    cout<<dp[m];
}

dp数组内容:

2 2 2 2 2
6 6 6 4
8 6 6
8 6
8

注:若要求达到最大价值时的最大体积需将初始化dp[N]={0}改为dp[0]=0,剩余dp为-INF; ans从直接输出dp[m]变为max(dp[0…m]).

状态方程的详细求解过程:在这里插入图片描述
二维优化成一维的推导:在这里插入图片描述

完全背包

题目:https://www.acwing.com/problem/content/3/

d p [ i ] [ j ] dp[i][j] dp[i][j]同样也是表示从前i个物品中选,总体积不超过j的方案集合,唯一的区别就是完全背包问题中每个物品都可以取无限个

所以可得到:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] , d p [ i − 1 ] [ j − 2 ∗ v [ i ] ] + 2 ∗ w [ i ] , . . . ) ​ dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]]+2*w[i],...)​ dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i],dp[i1][j2v[i]]+2w[i],...)
按道理来说需要三层循环,但是可以被优化成二层循环:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − v [ i ] ] + w [ i ] ) ​ dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])​ dp[i][j]=max(dp[i1][j],dp[i][jv[i]]+w[i]

具体证明:在这里插入图片描述
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,dp[1005][1005];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m,v[1005],w[1005];
    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=0;j<=m;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=v[i])dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
        }
    }
    cout<<dp[n][m];
}
二维优化成一维:

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − v [ i ] ] + w [ i ] ) ​ → d p [ j ] = m a x ( d p [ j ] , d p [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])​\to dp[j]=max(dp[j],dp[j-v[i]]+w[i]) dp[i][j]=max(dp[i1][j],dp[i][jv[i]]+w[i]dp[j]=max(dp[j],dp[jv[i]]+w[i])

与0-1背包不同的是,0-1背包的 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]对应的是i上一层也就是i-1层的值,而这里的 d p [ i ] [ j − v [ i ] ] dp[i][j-v[i]] dp[i][jv[i]]要求对应本层i的值,也就是让 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]先更新再更新 d p [ j ] dp[j] dp[j],所以把j从大到小循环改为从小到大循环即可。

优化后的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,dp[1005];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m,v[1005],w[1005];
    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=v[i];j<=m;j++){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m];
}

多重背包

题目(数据范围小):https://www.acwing.com/problem/content/4/
朴素做法(三维):

多一维遍历第i个物品选 0 ~ s [ i ] 0~s[i] 0s[i]个的所有情况

#include <iostream>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    int n,m;
    cin>>n>>m;
    int v[105],w[105],s[105],dp[105]={0};
    //memset(dp,0,105);
    for (int i = 1; i <=n ; ++i) {
        cin>>v[i]>>w[i]>>s[i];
    }
    for (int i = 1; i <=n ; ++i) {
        for (int j = m; j >=v[i] ; --j) {
            //cout<<dp[j]<<' ';
            for (int k = 1; k <=s[i] ; ++k) {
                if(j>=k*v[i])dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
            }
        }
        //cout<<'\n';
    }
    cout<<dp[m]<<endl;
//    for (int i = 0; i <=m ; ++i) {
//        cout<<dp[i]<<' ';
//    }
}
题目(数据范围大):https://www.acwing.com/problem/content/5/

思考到多重背包问题可以将每个物品拆开然后重新存入数组里,但如果直接拆开根据数据范围 s ∗ N ∗ V = 2000 ∗ 1000 ∗ 2000 = 2 ∗ 1 0 9 s*N*V=2000*1000*2000=2*10^9 sNV=200010002000=2109

还是会超过时间限制,所以考虑到将物品通过二进制的方法拆解:假如某物品有7件用1,2,4就可以表示全部可能。所以得出s件物品只需要 ⌈ l o g ( s ) ⌉ \lceil log(s) \rceil log(s)的时间,所以就是 11 ∗ 1000 ∗ 2000 ≈ 2 ∗ 1 0 7 11*1000*2000\approx2*10^7 11100020002107,可以在规定时间内通过。但在具体实现中还会有问题,假如某物品有10件,不能直接用1,2,4,8表示,因为1,2,4,8可以表示1~15,而11~15不是我们所需要的。所以采取一个策略:用s减去1,2,4…当s<0的时候停止,并将最后剩余的那个数存进数组。假如10, 10 − 1 − 2 − 4 = 3 10-1-2-4=3 10124=3,所以就将1,2,4,3存入数组。

#include <iostream>
#include <vector>

using namespace std;
struct struc {
    int v, w;
};
int v[2005], w[2005], s[2005], dp[2005];
vector<struc> vec;  //用结构体矢量存拆开的物品的数据

int main() {
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    //memset(dp,0,105);
    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i] >> s[i];
    }
    for (int i = 1; i <= n; ++i) {
        for (int k = 1; k <= s[i]; k *= 2) {
            s[i] -= k;
            vec.push_back({k * v[i], k * w[i]});
            //cout<<k<<' '<<"s[i]"<<s[i]<<' ';
        }
        //cout<<endl;
        if (s[i] > 0)vec.push_back({s[i] * v[i], s[i] * w[i]});  //将剩下的那个数存进矢量
    }
    for (int i = 0; i < vec.size(); ++i) {  
        for (int j = m; j >= vec[i].v; --j) {
            dp[j] = max(dp[j], dp[j - vec[i].v] + vec[i].w);
        }
    }
    cout << dp[m] << endl;
}

分组背包

题目:https://www.acwing.com/problem/content/9/
解析:在这里插入图片描述
代码:
#include<iostream>
#define pb push_back
using namespace std;
typedef long long ll;
int n,m,dp[105],s[105],v[105][105],w[105][105];
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]); //遍历第i组物品中的所有可能
            }
        }
    }
    cout<<dp[m];
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值