当前位置: 首页 > news >正文

动态规划入门:4种背包问题大纲

背包不一定要装满,背包容量一定,为V

N个物品,每个物品有wi价值、vi体积,问装备方法使得总价值最大

目录

概述:

一、0-1背包问题

 为什么0-1背包问题滚动数组法体积j要从大到小枚举:

二、完全背包问题

1.按顺序枚举方法:

2.优化方法:

 3.一维从小到大枚举体积j滚动数组写法:

为什么完全背包问题滚动数组法体积j要从小到大枚举:

 三、多重背包问题

1.顺序枚举法:

2.分析f数组元素意义后简化对比次数法:

四、分组背包问题

五、针对滚动数组写法小结


概述:

①0-1背包问题

    每个物品最多使用一次

②完全背包问题

    每个物品最可使用无数次,但是每个物品数量要保证k*v[i]<=j体积的k个

③多重背包问题

    不同种物品数量不同且有限

④分组背包问题

    多个物品分别分组,一组只能选一次

一、0-1背包问题

Dp问题

状态表示

f(i,j)

(与wi价值同量纲)

集合所有选法-
条件

①只从前 i 个物品种选

②总体积 ≤ j

属性(max、min、数量等)-
-
状态计算集合划分不含第i个物品f(i-1,j)
-
含第i个物品f(i-1,j-vi)+wi
-

最终  f(i,j) = max( f( i-1 , j ) , f( i-1 , j-vi ) + wi )

 上面标红的地方是个重点编码技巧所在,你可以认为f(i,j)也是叫含第i个物品不超过j体积,那标红的部分也可以这么

二维表示法:

#include<iostream>
#include<algorithm>
using namespace std;const int  N=1010;
int f[N][N];
int v[N],w[N];
int n,m;
int main()
{cin>>n>>m;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];//第几个物品只能从1开始for(int i=1;i<=n;i++)//有等于,包括第i个物品,第几个物品只能从1开始{for(int j=0;j<=m;j++)//有等于包括体积为j、0{f[i][j]=f[i-1][j];//不包括第i个物品if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//与原本f[i][j]作对比,看加第i个物品是的价值是多了还是少了//max函数第二个位置的编写是动态规划中最核心技巧。//总集合划分为第i个选或不选,max第一个变量为第i物品不选的划分,第二个变量为第i个物品选的划分}}cout<<f[n][m];return 0;}

 一维表示法(j体积从后往前缩小到v[i]的滚动数组):

#include<iostream>
#include<algorithm>
using namespace std;const int N=1010;
int n,m;int v[N],w[N];
int f[N];//下标表示总体积,内容表示价值int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i];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;
}

 为什么0-1背包问题滚动数组法体积j要从大到小枚举:

 会导致重复选择同一物品

 

 

二、完全背包问题

 

Dp问题

状态表示

f(i,j)

(与wi价值同量纲)

集合所有选法-
条件

①只从前 i 个物品种选

②总体积 ≤ j

属性(max、min、数量等)-
-
状态计算集合划分第i个物品0个f(i-1,j)
-
第i个物品选1、2、3...k个

f(i-1,j-k*vi)+k*wi

-

 然而以上两个状态的计算方程可以合并为f(i,j) =max( f(i,j) , f( i-1 , j-k*vi ) + k*wi )

1.按顺序枚举方法:

不重不漏的保证方法:第i个不选体积为j、第i个选k个(k=0、1...k)体积为 j-k*v[i]

#include<iostream>
#include<algorithm>
using namespace std;const int N=1010;
int f[N][N];
int v[N],w[N];
int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];for(int i=1;i<=n;i++){for(int j=0;j<=m;j++){for(int k=0;k*v[i]<=j;k++){f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);}}}cout<<f[n][m];
}

2.优化方法:

 考虑f[i][j]、f[i][j-v]元素的意义

实际上需要比较的只有f[i-1][j]与f[i][j-v]+w[i]元素两者取max

因此,f(i,j)只和两项有关,无需用k进行遍历操作。

#include<iostream>
#include<algorithm>
using namespace std;const int N=1010;
int f[N][N];
int v[N],w[N];
int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];for(int i=1;i<=n;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][j],f[i][j-v[i]]+w[i]);}}cout<<f[n][m];
}

与0-1背包问题的区别在于第二项的状态转移是从i-1转移过来还是从i转移过来,还要注意这个代码是推导式,直觉不太能直接进行理解,完全背包问题的简化代码一定是推导出来的,但的确有一步是从前一个状态转过来的过程f[i][j]=f[i-1][j]即第i个不选的情形;逻辑上有滚动数组的意思,即不需要每轮都全部枚举只需要每轮按需保证在整个过程中每种选择情形被对比一次即可。

0-1背包问题代码片段:

0-1背包问题第二项是从i-1转移过来(上为0-1背包问题,下为完全)

最后还要说一下,能如此优化的大前提就是f[i][j]中的每个元素都是各轮最大值,从而保证了可以滚动。

 3.一维从小到大枚举体积j滚动数组写法:

#include<iostream>
#include<algorithm>
using namespace std;const int N=1010;
int f[N];
int v[N],w[N];
int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];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];
}

0-1背包问题完全背包问题的滚动数组版本最后就只差在了前者体积从大到小枚举,后者体积从小到大枚举,想清楚这个区别与两问题特点之间的关系

为什么完全背包问题滚动数组法体积j要从小到大枚举:

若完全背包问题体积从大到小枚举则会导致无法多次选择同一物品:

 

 三、多重背包问题

每个物品有限si个

Dp问题

状态表示

f(i,j)

(与wi价值同量纲)

集合

①只从前 i 个物品种选

②总体积 ≤ j

-

-

属性max-
-
状态计算集合划分不含第i个物品f(i-1,j)
-
含第i个物品f(i-1,j-k*vi)+k*wi

k<=s[i]

 与0-1背包问题作对比,发现在0-1背包问题中需要先进行f[i][j]=f[i-1][j]的赋值操作,但是后面完全背包问题多重背包问题的顺序枚举方法中没有这一步先行赋值操作,为什么?

0-1背包问题代码片段:

 

 完全背包问题代码片段:

 

 因为在k=0时蕴含了与f[i][j]=f[i-1][j]的比较操作

1.顺序枚举法:

#include <iostream>
#include <algorithm>
using namespace std;
const  int N=110;
int f[N][N];
int s[N],v[N],w[N];
int n,m;int main()
{cin>>n>>m;for(int i=1;i<=n;i++)cin>>v[i]>>w[i]>>s[i];for(int i=1;i<=n;i++){for(int j=0;j<=m;j++){for(int k=0;k*v[i]<=j&&k<=s[i];k++){f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);}}}cout<<f[n][m];
}

2.分析f数组元素意义后简化对比次数法:

C++1s内最多完成10^7~10^8次计算,本题N*V*S优化为N*V*logS ,1000*2000*log2000≈2*10^7在范围内。

f[i][j]与完全背包问题的元素意义略有区别:

 二进制优化

例如我想用二进制加减来凑出200以内的所有数只需要以下几个数加减即可

#include<iostream>
#include<algorithm>
using namespace std;const int N=25000;//N种物品,每种物品数量使用二进制指数加减生成1000*log2000约2万个
int v[N], w[N];
int f[N];int main()
{int n, m;cin >> n >> m;//物品种类、背包体积int cnt = 0;//最外层循环是不同种物品,一轮外循环内部只有一种物品按照数量作划分for (int i = 1; i <= n; i++){int a, b, s;//a价值b重量s数量cin >> a >> b >> s;int k = 1;//幂块//划分while (k <= s){cnt++;//块内该种物品总体积、总价值v[cnt] = a * k;w[cnt] = b * k;//块的划分s -= k;k *= 2;}//bias尾块if (s > 0){cnt++;v[cnt] = a * s;w[cnt] = b * s;}}n = cnt;//n种物品整体为一个类别,总数量重新划分为cnt块//不同种类的物品对应下标cnt不一样,同种物品不同数量块的cnt下标也不一样不必担心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;return 0;
}

四、分组背包问题

 

Dp问题

状态表示

f(i,j)

(与wi价值同量纲)

集合

①只从前 i 个物品种选

②总体积 ≤ j

-

-

属性max-
-
状态计算集合划分不含第i个物品f(i-1,j)
-
枚举第i种物品,类内选哪一 个f(i-1,j-v(i,k))+w(i,k)

-

 

#include<iostream>
#include<algorithm>
using namespace std;const int N=110;
int f[N],s[N];
int v[N][N],w[N][N];
int n,m;//将大小n和v数组与m总体积区分开来
int main()
{cin>>n>>m;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--)//这里j到0和到1对结果没有影响{for(int k=1;k<=s[i];k++)if(j>=v[i][k])f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);}}cout<<f[m];}

五、针对滚动数组写法小结

用的是上一轮的状态则从大到小枚举体积,用的是本轮的状态的话则从小到大枚举体积

相关文章:

  • 拆机装机,通电主板亮灯风扇不转无法开机解决办法
  • 数据驱动、精准协同:高端装备制造业三位一体生产管控体系构建
  • POSIX 信号量(Semaphore)
  • 深入解析 Python 中的装饰器 —— 从基础到实战
  • 第六章 进阶04 尊重
  • 【Contiki】Contiki process概述
  • oasys 打开慢的问题解决
  • 2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(四级)真题
  • Spring Boot 3 + SpringDoc:打造接口文档
  • FPGA学习——DE2-115开发板上设计波形发生器
  • 软件项目验收报告模板
  • 算法备案的审核标准是什么?
  • 全国青少年信息素养大赛 C++算法创意实践挑战赛初赛 集训模拟试卷《七》及详细答案解析
  • chkconfig指令
  • Windows程序包管理器WinGet实战
  • HarmonyOS 基础语法概述 UI范式
  • cmd查询占用端口并查杀
  • 推荐一款Umi-OCR_文字识别工具
  • 一个好用的高性能日志库——NanoLog
  • 【学习笔记】Py网络爬虫学习记录(更新中)
  • 从板凳席到指挥台,横扫广东男篮的少帅潘江究竟有何神奇
  • 神舟二十号全系统合练今日展开
  • 美国税局代理局长卷入马斯克与美财长之争,还未工作就被迫离职
  • 在没有穹顶的剧院,和春天的音乐会来一场约会
  • 地铁口被吐槽像棺材?杭州地铁公司回应:是一个标志性出入口
  • 习近平会见柬埔寨太后莫尼列