算法 背包问题
目录
前言
一 01背包问题
二 二维费用的背包问题编辑
三 完全背包问题
总结
前言
这里是背包系列问题,需要知道动态规划地知识才可以理解这个一个东西,所以可以先去看看我的动态规划地文章
算法 动态规划-CSDN博客
一 01背包问题
首先背包问题是动态规划地十分经典的问题
首先我们自己动手把这个dfs版本和记忆化搜索自己写出来
dfs版本#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int dfs(int x, int bgv){ if(x > n) return 0; else if(bgv < bg[x]) return dfs(x+1, bgv); else return max(dfs(x+1 , bgv),dfs(x+1 , bgv-bg[x])+val[x]); } int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++) scanf("%d %d",&bg[i],&val[i]); int result = dfs(1,v); printf("%d",result); }
记忆化搜索版本
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int mem[N][N]; int dfs(int x, int bgv){ if(mem[x][bgv]) return mem[x][bgv]; int sum = 0; if(x > n) return 0; else if(bgv < bg[x]) sum = dfs(x+1, bgv); else sum = max(dfs(x+1 , bgv),dfs(x+1 , bgv-bg[x])+val[x]); mem[x][bgv] = sum; return sum; } int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++) scanf("%d %d",&bg[i],&val[i]); int result = dfs(1,v); printf("%d",result); }
这些在动态规划是有讲过的,所以这里就不多叙述了,接下来看看动态规划版本
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int dp[N][N]; int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++) scanf("%d %d",&bg[i],&val[i]); memset(dp ,0 , sizeof dp); //倒序 for(int i = n; i>=1 ;i--){ for(int j = 0; j<=v ;j++){ if( j < bg[i]) dp[i][j] = dp[i+1][j]; else{ dp[i][j] = max(dp[i+1][j],dp[i+1][j-bg[i]]+val[i]); } } } printf("%d\n",dp[1][v]); //正序 for(int i = 1; i<=n; i++){ for(int j = 0; j <= v; j++){ if(j < bg[i]) dp[i][j] = dp[i-1][j]; else{ dp[i][j] = max(dp[i-1][j],dp[i-1][j-bg[i]]+val[i]); } } } printf("%d\n",dp[n][v]); }
这里写了两个版本,一个是正序的一个是逆序的
倒序分析
我们可以先来看一个图
样例3 5 2 3 3 4 4 5
我们先进性最底下的判断,我们输入了3个物品,背包的数值是5
注意这个最后一行是十分关键的,所以一定要弄出一行且数字为0
行就是要拿第几个物品,列就是背包容量,为1的话就是1,为0就是0if( j < bg[i]) dp[i][j] = dp[i+1][j]; else{ dp[i][j] = max(dp[i+1][j],dp[i+1][j-bg[i]]+val[i]); }
这个是倒叙的核心代码
如果背包容器不够的话这个数组里面就填写上一行的值,也就是看上一行这个时候背包容量的最值,所以为什么要设置下一行为0,就是这个原因
如果可以存放,那么我们就要拿出最大值,我们就进行对比,也是这个时候背包容量,但是是拿上一个物品的最值,如果利用上一个物品的最值和这一次利用这个书包去装别的物品,看哪一个最大,哪一个更大就选择哪一个
正序,也就是和倒叙的表格是相反的,但是思路是正确的
因为倒叙我们是先拿最大的,所以正序不是先拿最大的,所以这个图会有些差别的,但是思路是一样
综上所述
dp的理解
动态规划01背包问题就是取上一行物品对应的最值和上一行减去这个背包容量的最值,然后放入当前新的物品进行比较看哪一个大就更新对应的位置
正序倒序的理解
正序就是减,为什么要减,因为我们是从上面开始排的
倒序就是加,为什么要加,因为我们是从下面开始排的滚动数组的降维
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int g[N]; int f[N]; int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++) scanf("%d %d",&bg[i],&val[i]); for(int i = 1; i<=n; i++){ for(int j = 0; j <= v; j++){ if(j < bg[i]) g[j] = f[j]; else{ g[j] = max(f[j],f[j - bg[i]] + val[i]); } } memcpy(f , g , sizeof f); } printf("%d\n",f[v]); }
我们不难从上面那个例子可以看出这个最后的结果始终与它的周围的一行像关联,那么我们就可以使用两个数组进行滚动,那么就可以不用开辟那么多的空间,实现了空间的优化
二 二维费用的背包问题
首先我们还是用dfs进行书写,这个就是多了一个重量
#include<iostream> #include<cstring> #include<cstring> using namespace std; const int N = 1010; int n,v,m; int bg[N]; int mg[N]; int val[N]; int dfs(int x, int bgv, int bgm){ if(x > n) return 0; else if(bgv < bg[x] || bgm < mg[x]) return dfs(x+1 ,bgv ,bgm); else return max(dfs(x+1, bgv,bgm) , dfs(x+1,bgv - bg[x],bgm - mg[x]) + val[x]); } int main(){ scanf("%d %d %d",&n,&v,&m); for(int i = 1; i<n ;i++){ scanf("%d %d %d",&bg[i],&mg[i],&val[i]); } int result = dfs(1,v,m); printf("%d",result); return 0; }
这个肯定是过不去得,我们接下来用记忆化搜索试试
#include<iostream> #include<cstring> #include<cstring> using namespace std; const int N = 1010; int n,v,m; int bg[N]; int mg[N]; int val[N]; int mem[N][100][100]; int dfs(int x, int bgv, int bgm){ if(mem[x][bgv][bgm]) return mem[x][bgv][bgm]; int sum = 0; if(x > n) sum = 0; else if(bgv < bg[x] || bgm < mg[x]) sum = dfs(x+1 ,bgv ,bgm); else sum = max(dfs(x+1, bgv,bgm) , dfs(x+1,bgv - bg[x],bgm - mg[x]) + val[x]); mem[x][bgv][bgm] = sum; return sum; } int main(){ scanf("%d %d %d",&n,&v,&m); for(int i = 1; i<n ;i++){ scanf("%d %d %d",&bg[i],&mg[i],&val[i]); } int result = dfs(1,v,m); printf("%d",result); return 0; }
记忆化搜索是通过了,但是这个三维数组太大了,所以不推荐,下面我们用动态规划进行书写
#include<iostream> #include<cstring> #include<cstring> using namespace std; const int N = 1010; int n,v,m; int bg[N]; int mg[N]; int val[N]; int f[N][110][110]; int main(){ scanf("%d %d %d",&n,&v,&m); for(int i = 1; i<n ;i++){ scanf("%d %d %d",&bg[i],&mg[i],&val[i]); } for (int i = n; i >= 1; i--) { for (int j = 0;j <= v;j++) { for (int k = 0; k <= m;k++) { if (j < bg[i] || k < mg[i]) f[i][j][k] = f[i + 1][j][k]; else { f[i][j][k] = max(f[i + 1][j][k], f[i + 1][j - bg[i]][k - mg[i]] + val[i]); } } } } printf("%d",f[1][v][m]); return 0; }
ok只能说学完动态规划,这个真的是太简单了
三 完全背包问题
这个题目就是实现了物品无限次拿,这个就是与01背包得差别
我们可以看到01背包得dfsint dfs(int x, int bgv){ if(x > n)return 0; else if(bgv < bg[x]) return dfs(x+1, bgv); else return max(dfs(x+1, bgv),dfs(x+1, bgv - bg[x])+ val[x]); }
我们可以看到这个dfs,这个其实是有两个x+1的,也就是说这个+1限制了它无限拿去的条件,那么我们只需要删除一个+1就好了
dfs#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int dfs(int x, int bgv){ if(x > n)return 0; else if(bgv < bg[x]) return dfs(x+1, bgv); else return max(dfs(x+1, bgv),dfs(x, bgv - bg[x])+ val[x]); } int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++){ scanf("%d %d",&bg[i],&val[i]); } int result = dfs(1,v); printf("%d",result); }
当我们在写这个代码的时候,我们是删除了后面的x+1的+1,但是有人就会说,为什么不可以删除前面的一个,因为我们删除前面的,按照原理上是应该可以的,但是你要知道递归式先递归前面的,也就是说你这个x一直都是x那么就会生成内存超出限制的错误,也就是S了,所以改动后面的
记忆化搜索#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int mem[N][N]; int dfs(int x, int bgv){ if(mem[x][bgv]) return mem[x][bgv]; if(x > n)return 0; int sum = 0; if(bgv < bg[x]) sum = dfs(x+1, bgv); else sum = max(dfs(x+1, bgv),dfs(x, bgv - bg[x])+ val[x]); mem[x][bgv] = sum; return sum; } int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++){ scanf("%d %d",&bg[i],&val[i]); } int result = dfs(1,v); printf("%d",result); }
dp
我们来把这个转换为dp#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; int n,v; int bg[N]; int val[N]; int f[N][N]; int main(){ scanf("%d %d",&n,&v); for(int i = 1; i <= n; i++){ scanf("%d %d",&bg[i],&val[i]); } for(int i = n;i >= 1; i--){ for(int j = 0; j <= v; j++){ if(j < bg[i]) f[i][j] = f[i+1][j]; else { f[i][j] = max(f[i+1][j],f[i][j - bg[i]] + val[i]); } } } printf("%d",f[1][v]); }
总结
我们还是学习了dsf,记忆化,动态规划
这个背包问题就是需要一个max来求取最优子问题,然后前面再加几个判断条件就好了
然后记忆化搜索就是创建一个变量进行保存,然后再创建一个数组进行保存数据就好了
01背包式需要x+1进行限制,完全背包不需要
二维费用背包就是需要条件的限制而已
需要注意的是,我们需要进行限制的条件就是我们函数的形参