背包九讲_模板整理
背包九讲模板整理
目录,点击跳转
01背包
例题:
P1048 [NOIP2005 普及组] 采药
P1734 最大约数和
P1060 [NOIP2006 普及组] 开心的金明
一维数组,逆序循环(避免一件物品重复取)
//一维优化 for(int i=1;i<=n;i++){ for(int j=m;j>=v[i];j--){ if(j>=v[i]) dp[j]=max(dp[j],dp[j-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]); } }
多重背包
加了限制条件的完全背包
规定了每件物品的最多数量
优化前
按照01背包的方式处理
为啥不按照完全背包的方式,正序循环呢?
01背包为了避免物品重复\多拿,所以才采用倒序循环
完全背包的单个物品数量是无限的,所以正序循环不会影响
而多重背包的数量是有限的,如果正序循环可能会超出物品数量,所以倒序
时间复杂度是O(n*m)级别
for(int i=1;i<=n;i++){ for(int j=m;j>=v[i];j--){ for(int k=0;k<=p[i]&&k*v[i]<=j;k++){ dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]); } } }
二进制优化
优化后
时间复杂度O(n*log)级别
二进制优化 for (int i = 1; i <= n; i++) { int num = min(p[i], m / v[i]); // V/c[i]是最多能放多少个进去,优化上界 for (int k = 1; num > 0; k <<= 1) { //这里的k就相当于上面例子中的1,2,4,6 if (k > num) k = num; num -= k; for (int j = m; j >= v[i] * k; j--) // 01背包 f[j] = max(f[j], f[j - v[i] * k] + w[i] * k); } } cout<<f[m];
单调队列优化
使用二进制优化,能够达到O(n*log)级别
使用单调队列可以达到O(n)
使用单调队列维护区间最值问题
数组实现单调队列
#include<bits/stdc++.h> using namespace std; const int N=2000010; int n,m; int f[N],g[N]; int q[N]; int main(){ cin>>n>>m; int v,w,s; for(int i=1;i<=n;i++){ cin>>v>>w>>s; memcpy(g,f,sizeof(f)); for(int j=0;j<v;j++){ //因为背包容量可以为0,所以从h=0,t=-1开始 int h=0,t=-1; for(int k=j;k<=m;k+=v){ if(h<=t&&k-s*v>q[h]) h++; while(h<=t&& g[k]>=g[q[t]]+(k-q[t])/v*w) t--; if(h<=t) f[k]=max(g[k],g[q[h]]+(k-q[h])/v*w); q[++t]=k; } } } cout<<f[m]<<endl; }
双端队列实现单调队列
效率不如第一种方式
推荐使用上面的数组实现单调队列
for(int i=1;i<=n;i++){ cin>>v>>w>>s; for(int j=0;j<v;j++){ memcpy(g,f,sizeof(f)); q.clear(); for(int k=j;k<=m;k+=v) { if(!q.empty()&&k-s*v>q.front()) q.pop_front(); while(!q.empty()&&g[k]>=g[q.back()]+(k-q.back())/v*w) q.pop_back(); if(!q.empty()) f[k]=max(g[k],g[q.front()]+(k-q.front())/v*w); q.push_back(k); } } } cout<<f[m];
混合背包
以上三种背包的混合版
for(int i=1;i<=n;i++){ if(p[i]==-1){//01背包 for(int j=m;j>=v[i];j--){ dp[j]=max(dp[j],dp[j-v[i]]+w[i]); } } else if(p[i]==0){//完全背包 for(int j=v[i];j<=m;j++){ dp[j]=max(dp[j],dp[j-v[i]]+w[i]); } } else{//多重背包二进制优化 int num=min(p[i],m/v[i]) ; for(int k=1;num>0;k<<=1){ if(k>num) k=num; num-=k; for(int j=m;j>=k*v[i];j--){ dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]); } } } }
二维费用背包
将一件物品放入背包需要付出两种代价
//01背包问题 for (int i = 1; i <= n; i++) for (int j = m; j >= v[i]; j--) for (int k = p; k >= c[i]; k--) f[j][k] = max(f[j][k], f[j - v[i]][k - c[i]] + w[i]);
//完全背包问题,只需要正序即可 for (int i = 1; i <= n; i++) for (int j = v[i]; j <= m; j++) for (int k = c[i]; k <= p; k++) f[j][k] = max(f[j][k], f[j - v[i]][k - c[i]] + w[i]);
分组背包
例题:
分组背包问题
P1757 通天之分组背包
分组背包的时间复杂度难以优化,因为每一组的物品不同,难以分类
但是还是可以用滚动数组来优化空间
使用一维数组,必须要确定体积去循环物品,因为每组只能选一个物品
#include<bits/stdc++.h> using namespace std; int n,m; int v[10005],w[10005]; int f[10005]; int main(){ cin>>n>>m; int s; for(int i=1;i<=n;i++){ cin>>s; for(int j=1;j<=s;j++) cin>>v[j]>>w[j]; for(int j=m;j>=0;j--){//固定体积 for(int k=1;k<=s;k++){//枚举物品 if(j>=v[k]) f[j]=max(f[j],f[j-v[k]]+w[k]); } } } cout<<f[m]; }
依赖背包
典型的树形DP
洛谷P1352.没有上司的舞会
- 递归写法
//树形DP 就是一个dfs的过程,在这个过程中找最大值 #include<bits/stdc++.h> using namespace std; const int N = 6005; //DP数组 //f[i][1]选这个人 //f[i][0]不选这个人 int f[N][N] , w[N]; //fa 记录结点有没有父节点 bool fa[N]; vector<int> a[N];//记录编号为i的领导手下的员工的编号 int n; //深搜下去,在回溯时进行dp void dfs(int u){ f[u][1] = w[u];//选u的快乐指数 for(int i = 0 ;i < a[u].size(); i++){ int son = a[u][i];//获取u的子节点 dfs(son); f[u][0] += max(f[son][0],f[son][1]); f[u][1] += f[son][0]; } } int main(){ cin >> n; for(int i = 1; i <= n; i++) cin >> w[i]; for(int i = 0 ;i < n - 1 ; i ++){ int x , y; // 员工---上司 cin >> x >> y; //关于这棵树如何存储这件事 a[y].push_back(x); fa[x] = true;// x有父节点 } int root = 1; //找根节点 while(fa[root]) root ++; dfs(root); cout << max(f[root][0],f[root][1]); return 0 ; }
泛化物品
这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念
更严格的定义之。在背包容量为V VV的背包问题中,泛化物品是一个定义域为0... V 0...V0...V中的整数的函数h hh,当分配给它的费用为v时,能得到的价值就是h ( v ) h(v)h(v)。
背包问题演化
求具体方案
推荐视频教学
董晓算法__求背包具体方案
//在达到最大价值时,输出字典序最小的方案 /* 假设存在包含第1个物品的最优解,为了确保字典序最小, 我们必然要选择第一个物品 那么 问题就转化成了 2~N这些物品中找到最优解 首先,从后向前遍历物品,让最优解落在f[1][m]中; 然后,从f[1][m]开始搜索字典序最小的路径方案 状态定义: f[i][j]表示从最后一个物品到第i个物品,装入容量为j的背包的最大价值 状态转移: f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i] 寻找路径时,正序查找: 如果 f[i][j]=f[i+1][j],表示 不选第i个物品才可以最大价值 如果f[i][j]=f[i+1][j-v[i]]+w[i],必须选第i个物品才能最大价值 如果f[i][j]=f[i+1][j]=f[i+1][j-v[i]]+w[i] 选不选第i个物品都可以达到最大价值,但是为了保证字典序最小 第i个物品也必须选 !!! 结论: 如果f[i][j]能通过f[i+1][j-v[i]]+w[i] 转移得到,就选第i个物品 */ #include<bits/stdc++.h> using namespace std; const int N=1005; int f[N][N]; int n,m; int v[N] , w[N]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin >> v[i] >> w[i]; //逆序取物 for(int i=n;i>=1;i--){ for(int j=0;j<=m;j++) f[i][j]=f[i+1][j]; if(j>=v[i]) f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i]); } //正序寻找 int j=m;//剩余容量 for(int i=1;i<=n;i++){ //如果当前的价值是由 后面几种 加上第一种 得来的,就选 if(j>=v[i] && f[i][j]==f[i+1][j-v[i]]+w[i]){ cout<<i<<' '; j-=v[i]; } } return 0; }
求背包方案数
推荐视频教学
董晓算法___求背包方案数量
能达到最大价值的方案数
例如: 01背包求方案数
其他背包只需要在前面的循环条件上做出调整即可
#include<bits/stdc++.h> using namespace std; const int N = 1005; int n,m; //f[i]容量为i时的最大容量,c[i]达到最大值时的方案 int f[N],c[N]; int mod=1e9+7; int main(){ cin >> n >> m; //背包方案初始化 for(int i=0;i<=m;i++) c[i]=1; int v,w; for(int i=1;i<=n;i++){ cin>>v>>w; for(int j=m;j>=v;j--){ if(f[j-v]+w>f[j]){ f[j]=f[j-v]+w; c[j]=c[j-v]; } else if(f[j-v]+w==f[j]) c[j]=(c[j]+c[j-v])%mod; } } cout<<c[m]; }
装满背包求方案数
在装满背包的前提下,.所能达到的最大价值的方案数
只需要初始化一下两个数组
#include<bits/stdc++.h> using namespace std; const int N = 1005; int f[N],c[N]; int n,m; int mm=-1000005;//定义一个最小值 int main(){ cin >> n >> m; for(int i=1;i<=m;i++) f[i]=mm;//将每个背包的价值初始化为最小值 f[0]=0,c[0]=1; int v,w; for(int i=1;i<=n;i++){ cin >> v >> w; for(int j=m; j>=v;j--){ if(f[j-v]+w>f[j]){ f[j]=f[j-v]+w; c[j]=c[j-v]; } else if(f[j-v]+w==f[j]) c[j]=c[j]+c[j-v]; } } //只有恰好装满背包时, 一定是从c[0]直接或间接转移而来 //所以只给 c[0]=1 其他的c[i]=0, 0+0=0,不计入方案数 cout << c[m]; return 0; }
参考文章: 背包九讲——全篇详细理解与代码实现
作者: 良月澪二
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!