背包九讲_模板整理

背包九讲模板整理

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]);
}
}

完全背包

P1616 疯狂的采药

一位数组,正序转换

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]);
}
}
}
}

二维费用背包

将一件物品放入背包需要付出两种代价

P1507 NASA的食物计划

//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.没有上司的舞会

  1. 递归写法
//树形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;
}

参考文章: 背包九讲——全篇详细理解与代码实现
作者: 良月澪二

posted @   秋天Code  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示