背包问题
背包问题
01背包问题
n个物品,每个物品的体积是vi,价值是wi,背包的容量是j
若每个物品最多只能装一个,且不能超过背包容量,则背包的最大价值是多少?
int n; //物品数量
int m; //背包容量
int v[N]; //体积
int w[N]; //价值
//二维朴素版本
int f[N][M];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(j<v[i]) f[i][j]=f[i-1][j];
else f[i][j]=max(f[i-1][j],f[i-1,j-v[i]]+w[i])
cout<<f[n][m]
//一维版本
int f[M];
for(int i=1;i<=m;i++)
for(int j=m;j>=v[i];j--)//注意是倒序,否则写后读
f[j]=max(f[j],f[j-v[i]]+w[i])
cout<<f[m]
二维压缩成一维,实际上是寻找避开写后读错误的方法: 由于f[i][j]始终只用上一行的数据f[i-1][...]更新(迭代更新的基础,如果还需用上上行数据则不可压缩) 且f[i][j]始终用靠左边的数据f[i-1][<=j]更新(决定了只能倒序更新) 举例说明: 二维:dp[3][8] = max(dp[2][8], dp[2][3] + w[3]) 此时的dp[2][8]和dp[2][3]都是上一轮的状态值 一维:dp[8] = max(dp[8], dp[3] + w[3]) 我们要保证dp[8]和dp[3]都是上一轮的状态值 按照逆序的顺序,一维dp数组的更新顺序为:dp[8], dp[7], dp[6], ... , dp[3] 也就是说,在本轮更新的值,不会影响本轮中其他未更新的值!较小的index对应的状态是上一轮的状态值! 如果按照顺序进行更新,dp[3] = max(dp[3], dp[0] + w[0]),对dp[3]的状态进行了更新,那么在更新dp[8]时,用到的dp[3] 就不是上一轮的状态了,不满足动态规划的要求。 例子来自链接:https://www.acwing.com/file_system/file/content/whole/index/content/2978/
完全背包问题
每个物品可以取任意个
假设背包容量为jj时,最多可装入k个物品i,则有
考虑
上式变形得
综上可得
//二维形式
int f[N][M]; // f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(j < v[i]) f[i][j] = f[i-1][j]; // 当前重量装不进,价值等于前i-1个物品
else f[i][j] = max(f[i-1][j], f[i][j-v[i]] + w[i]); // 能装,需判断
cout << f[n][m];
// ---------------一维形式---------------
int f[M]; // f[j]表示背包容量为j条件下的最大价值
for(int i = 1; i <= n; ++i)
for(int j = v[i]; j <= m; ++j)//正序
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m]; // 注意是m不是n
形式上和01背包差不多,在二维数组表示下,主要差别在: 在选择第i物品时,用的是f[i][j-v]+w,而不是f[i-1][j-v]+w 上述条件决定了在每次迭代时,必须正向遍历,而不是反向遍历 在一维数组表示下,主要差别只表现为迭代的顺序(正向或反向) 在一维数组表示下,01背包只能反向是因为它主要用到上一行的数据来更新当前行数据,如果正向遍历,则会修改上一行的数据,出现写后读错误;完全背包只能正向是因为它需要用到当前行的数据更新,如果反向遍历,使用的是上一行的数据,则不符合公式
多重背包问题
第i个物品至多拿si件
而
变形后得
多了一项
因此无法按照完全背包的方式优化
二进制优化
已知可以由系数0和1线性组合出。考虑更一般的情况,若想线性组合出,则猜测可由组合出,其中,显然,在C一定存在的情况下,可得到的数的范围为C-S。由于,则,故,即可用表示任何的数
因此对于有s[i]件的某个物品i,可以打包成个物品,每包有1,2,4,⋯,2k,C件物品i,其中
// -----------------未优化(完全背包模板)----------------------
int f[N][M]; // f[i][j]表示在考虑前i个物品后,背包容量为j条件下的最大价值
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int k = 0; k <= s[i] && k * v[i] <= j; k++)
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
// -----------------------二进制优化---------------------------
// 读入物品个数时顺便打包,a,b,s 为体积价值数量
int k = 1; // 当前包裹大小
int cnt=0;
while (k <= s)
{
cnt ++ ; // 实际物品种数
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2; // 倍增包裹大小
}
if (s > 0)
{
// 不足的单独放一个,即C
cnt ++ ;
v[cnt] = a * s;
w[cnt] = b * s;
}
n = cnt; // 更新物品种数
// 转换成01背包问题
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
用二进制优化后,注意物品种数变成N×logMN×logM,问题转换成01背包问题
时间复杂度为O(nmlogs)
分组背包问题
每组物品中至多拿1个
实际上是带有约束的01背包问题,状态计算为
解法:
int n; // 物品总数
int m; // 背包容量
int v[N][S]; // 重量
int w[N][S]; // 价值
int s[N]; // 各组物品种数
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 1; j <= s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
// 处理数据
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 1; j -- )
for (int k = 1; k <= s[i]; k ++ )
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
cout << f[m] << endl;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律