动态规划-背包问题

简单背包(01背包)

例:https://oj.czos.cn/p/1282

有n件物品,已知每件物品的价值v[i]和重量w[i],背包容量为c,要求从这n件物品中任取若干件装入背包内,使背包的物品价值最大。

未优化写法

设dp[i][j]表示有i件物品,在背包容量为j的情况下的最大价值度,则最终的答案为dp[n][c]

分析dp数组的状态转移方程:
对于第i件物品,背包容量为j

  1. 如果w[i]>j,则放不下,那么dp[i][j]=dp[i1][j]
  2. 如果w[i]<=j, 放得下, 那么可以选,也可以不选,即dp[i][j]=max(dp[i1][j],v[i]+dp[i1][jw[i]])
#include <bits/stdc++.h>
using namespace std;
int n,c,w[20010],v[110],dp[110][20010];
int main()
{
scanf("%d %d",&c,&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&w[i],&v[i]);
}
//动态规划
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
if(w[i]>j) dp[i][j]=dp[i-1][j];
else dp[i][j]=max(dp[i-1][j],v[i]+dp[i-1][j-w[i]]);
}
}
//输出答案
printf("%d",dp[n][c]);
return 0;
}

优化写法-滚动数组

dp二维数组当中有很多空间是浪费的,因为第i行dp值的计算只和第i-1行有关,所以可以只用一个一维数组,实现滚动数组的方式来维护dp数组
方法:用一维dp数组,从后往前更新,这样dp[j]相当于原先的dp[i-1][j],而dp[j-w[i]]相当于原先的dp[i-1][j-w[i]]
注意,dp数组要从后往前更新,否则会覆盖原先数组的值,导致dp[i-1][j-w[i]]取到错误的值
此时状态转移方程变为:

dp[j]=max(dp[j],dp[jw[i]])

这一步调整之后,时间复杂度和空间复杂度都会有很大的提升

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
int n,wi,vi,c;
int dp[maxn];
int main()
{
scanf("%d %d",&c,&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&wi,&vi);
//wi之前的相当于放不下的情况 可以不用算 直接继承自上一次的值
for(int j=c;j>=wi;j--)
{
//进来之后相当于之前的放得下的情况 需要在选和不选当中取出最大值
dp[j]=max(dp[j],vi+dp[j-wi]);
}
}
//输出答案
printf("%d",dp[c]);
return 0;
}

完全背包

例:https://oj.czos.cn/p/1780
和01背包不同的是,每件物品有无限多个可以选择。
设dp[i][j]表示有i件物品,在背包容量为j的情况下的最大价值,那么很容易联想到完全背包的状态转移方程就是

dp[i][j]=max(dp[i1][j],dp[i1][jkw[i]]+kv[i])(1<=k<=w/w[i])

下面对这个方程进行进一步的推导 尝试去掉k:

经过推广可以得到等价的方程:

dp[i][j]=max(dp[i1][j],max(dp[i1][jkw[i]]+kv[i]))(1<=k<=w/w[i])

其中的k先拿一个出来,得到

dp[i][j]=max(dp[i1][j],max(dp[i1][(jw[i])kw[i]]+kv[i])+v[i])(1<k<=w/w[i])

将①中k的范围扩大,可以得到③

dp[i][j]=max(dp[i1][jkw[i]]+kv[i])(0<=k<=w/w[i])

将③中的j替换为j-w[i]之后得到

dp[i][jw[i]]=max(dp[i1][(jw[i])kw[i]]+kv[i])(0<=k<=w/w[i])

将④带入②,可以得出完全背包问题的最终方程:

dp[i][j]=max(dp[i1][j],dp[i][jw[i]]+v[i])

与01背包的区别仅仅只是dp[i][jw[i]]+v[i]dp[i1][jw[i]]+v[i]的区别

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int c,n,vi,wi;
int dp[maxn];
int main()
{
//dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i])
scanf("%d %d",&c,&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&wi,&vi);
//wi之前的不用计算 相当于之前的放不下的情况
for(int j=wi;j<=c;j++)
{
//wi开始 相当于之前的放得下的情况 正序递推dp数组
dp[j]=max(dp[j],dp[j-wi]+vi);
}
}
printf("%d",dp[c]);
return 0;
}

多重背包 https://oj.czos.cn/p/1888

和01背包不同的是,每件物品有Si件

#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
int n,c;
int w[maxn],v[maxn],s[maxn],dp[maxn];
int main()
{
scanf("%d %d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d %d %d",&w[i],&v[i],&s[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=s[i];j++)
{
for(int k=c;k>=w[i];k--)
{
dp[k]=max(dp[k],dp[k-w[i]]+v[i]);
}
}
}
printf("%d\n",dp[c]);
return 0;
}

多重背包的二进制优化 https://oj.czos.cn/p/1889

二进制优化是一种压缩的思想
(1)有n个不同的物品,要讨论2n种选择的可能(每个物品选或者不选):
(2)相同的物品有n件,虽然要讨论2n种选择的可能,但由于n个物品是一样的,那么就减少了讨论数量,比如:有4个物品,如果是不同物品的选2个,选12、23是不同的选择,但如果是相同的物品,选哪两个就都是一样的了。因此,n个物品,要讨论的可能就分别是:选0个、选1个、选2个、选3个…选n个。
(3)要将0-n个不同的选择表达出来,比较简单的方法是将n二进制化。比如:整数7,只需要用124三个数任意组合,就能组合出0-7这8种可能。再比如:整数10,只需要用1243(注意最后一个数),就能组合出0-10这11种可能,这样n这个值就被二进制化了。因此如果要讨论10个一样的物品,就转化为讨论4个不同的物品了;而n个一样的物品,就转化为log2n个不同的物品进行讨论。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 11100;
int n,c,k,wi,vi,si;
int w[maxn],v[maxn],s[maxn],dp[maxn];
int main()
{
scanf("%d %d",&n,&c);
//读入数据 同时进行二进制压缩
for(int i=1;i<=n;i++)
{
scanf("%d %d %d",&wi,&vi,&si);
int t = 1;
while(t<=si)
{
k++;
w[k] = t*wi;
v[k] = t*vi;
si -= t;
t *= 2; //注意这里要放在后面
}
if(si>0)
{
k++;
w[k] = si*wi;
v[k] = si*vi;
}
}
//01背包
for(int i=1;i<=k;i++)
{
for(int j=c;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[c]);
return 0;
}

混合背包 https://oj.czos.cn/p/1905

二进制压缩后就只剩下01背包和完全背包了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10500;
int vi,wi,si,n,c,k;
int v[maxn],w[maxn],s[maxn],dp[maxn];
int main()
{
scanf("%d %d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d %d %d",&wi,&vi,&si);
//多重背包转换为01背包
if(si>0){
int t = 1;//权重
while(t<=si)
{
k++;
v[k] = t*vi;
w[k] = t*wi;
s[k] = -1;
si -= t;
t *= 2;
}
if(si>0)
{
k++;
v[k] = si*vi;
w[k] = si*wi;
s[k] = -1;
}
}else{
//01背包和完全背包都是正常存储即可
k++;
w[k] = wi;
v[k] = vi;
s[k] = si;
}
}
//剩下01背包和完全背包的dp
for(int i=1;i<=k;i++)
{
//01背包
if(s[i]==-1)
{
for(int j=c;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}else{
//完全背包
for(int j=w[i];j<=c;j++)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
//输出答案
printf("%d",dp[c]);
return 0;
}
posted @   Zhe8468  阅读(89)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示