背包问题【做题笔记】

个人做题笔记,仅供参考,如有错误并非巧合。

01背包、完全背包、多重背包的联系

        01背包是最基础的背包问题,基本上只要学会01背包,完全背包和多重背包都能很容易理解。01背包的特点是物品只能用一次,完全背包则是能用无数次,多重背包可以用有限次。后两者都可以在01背包上进行简单的修改形成。

01背包

比较经典的

01分为二维和一维两个写法。当题目要求输出物品i时通常用二维数组。比如这题

        二维dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])。

        前者是不加物品i的情况,后者是加物品i的最佳情况。初始化需要注意的是,如果背包最大容量为baseweight,那么设置时要vector<vector<int>>dp(n,vector<int>(baseweight+1,0));如果物品i从1开始算,初始化只要全部化为0即可。如果物品从0开始算,初始化还要额外对i=0行多进行一步(因为i-1防止越界。相当于自己提前算一下第一行的数据):

for (int j = weight[0]; j <= bagweight; j++) {//bagweight背包最大容量
    dp[0][j] = value[0];
}

        一维dp[j]=max(dp[j],dp[j-weight[i]]+value[i]).

        一维相当于是二维的简化版,省下了空间。把所有一维合起来就是二维。

        值得注意的是,二维的遍历顺序可以替换,但是一维必须先遍历物品再遍历背包(因为背包必须逆序遍历,不能放在前面,否则dp[j]只会放入一个物品);二维可以正序遍历也可以逆序遍历,一维遍历背包时必须逆序遍历(因为物品只能放一次。i行是从i-1行得到的,如果从前往后覆盖,i行后面的数据从前面(也就是本层i行数据)得出,而非i-1行),不然会重复加上之前的物品。为了习惯,我选择全部先遍历物品且逆序遍历背包。

完全背包

前面说到一维数组遍历背包时由于会重复加上之前的物品,而不能正序遍历。那么在完全背包中,这是完全允许的,因为完全背包的物品有无限个!那么既然正序逆序无所谓,遍历顺序也可以替换了。

但这是在题目没要求凑成总和的元素的顺序关系上,如果要求了无序或有序(即求的是组合数或排列数),遍历顺序就有说法了。(稍微提一下,因为是求排列组合,会初始化dp[0]=1)

先下结论:

        如果要求组合数,先遍历物品再遍历背包

        如果要求排列数,先遍历背包再遍历物品

组合数

排列数

比如排列数题目里面的样例1。先遍历nums的话,就只有{1,1,2},不会再有{1,2,1},{2,1,1}的情况。因为是按照nums的顺序来的。而如果先遍历背包的话,nums会遍历很多次,就会出现样例中排列的情况。

多重背包

请看这道题,我的AC代码在最后

多重背包可以转换为01背包,只要把那些背包全部都一个个摊开,就变成01背包了!

方式有两种:

①直接摊开,在weight和value数组后面直接加上多个数的物品。但是此方法可能会超时。

②三层循环。添加第三层循环。

for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
                dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);

 在01问题中,k相当于等于1,而在多重背包中,k可以等于很多数。

AC代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;

vector<int>nums(6);//个数 
int weight[6]={1,2,3,5,10,20}; 
//vector<int>dp(1005);
bool b[1005]={false};

void bag(){
	b[0]=true;
	for(int i=0;i<6;i++){//砝码 
		for(int j=1000;j>=0;j--){//重量
			for(int k=1;k<=nums[i]&&j+k*weight[i]<=1000;k++){//个数 
				if(b[j]){
					b[j+k*weight[i]]=true;
				}
			}
		}
	}
	int total=0;
	for(int i=1;i<=1000;i++){
		if(b[i]){
			total++;
		}
	}
	cout<<"Total="<<total;
}

int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	for(auto &i:nums){
		cin>>i;
	}
	bag();
	return 0;
 } 

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值