引言

在动态规划问题中,首先需要抽象出原问题的状态,然后写出状态转移方程,最后根据边界状态值和转移方程求解所有的状态。在本文中,我们将以01背包问题为例来探讨分析如何确定边界状态和边界状态值。

01背包问题的最优方案数(原题链接)

题目描述

有 n n n件物品,每件物品的重量为 w i w_i wi​,价值为 c i c_i ci​。现在需要选出若干件物品放入一个容量为 V V V的背包中(每件物品至多选一次),使得在选入背包的物品重量之和不超过容量 V V V的前提下,让背包中物品的价值之和最大,求最大价值与对应的最优方案数。

输入描述

第一行两个整数 n n n、 V V V ( 1 ≤ n ≤ 100 , 1 ≤ V ≤ 1 0 3 ) (1 \le n\le 100, 1 \le V \le 10^3) (1≤n≤100,1≤V≤103),分别表示物品数量、背包容量;

第二行为用空格隔开的 n n n​个整数 w i w_i wi​( 1 ≤ w i ≤ 100 1\le w_i \le100 1≤wi​≤100​),表示物品重量;

第三行为用空格隔开的 n n n​个整数​ c i c_i ci​( 1 ≤ c i ≤ 100 1\le c_i \le 100 1≤ci​≤100​),表示物品价值。

输出描述

输出两个整数,分别表示最大价值与最优方案数,中间用空格隔开。由于结果可能很大,因此将结果对
取模后输出。

样例1

输入

3 5
1 2 5
4 2 6

输出

6 2

解释
假设物品编号为从1到3,那么选择第1、2件物品、或者只选择第3件物品时,物品重量不会超过背包容量,对应的价值是 4 + 2 = 6 4+2=6 4+2=6​​,是最大价值。因此共有2种最优方案。

问题分析

这个问题需要求解两个值,分别是最大价值和最优方案数。

最大价值

对于最大价值,令dp[i][v]表示从前i件物品中选择若干件装入容量为v的背包,在不超过背包容量的前提下,所能装入的最大物品价值。其状态转移方程为:
dp[i][v] = { dp[i-1][v] , if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i] dp[i-1][v-w[i]] + c[i] , else \text{dp[i][v]}=\left\{ \begin{aligned} &\text{dp[i-1][v]}, \quad\quad \quad\quad \quad \text{if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]} \\ &\text{dp[i-1][v-w[i]] + c[i]},\quad \text{else} \\ \end{aligned} \right. dp[i][v]={​dp[i-1][v],if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]dp[i-1][v-w[i]] + c[i],else​
接下来确定上述状态转移方程的边界。对于dp数组的第一维,计算dp[1][v]时,需要用到dp[0][v],同时发现无法通过状态转移方程计算dp[0][v],因此dp[0][v] ( 0 ≤ v ≤ V 0 \le v \le V 0≤v≤V)就是一组边界。对于dp数组的第二维,计算dp[i][0]时,需要用到dp[i-1][0],通过递推可知最终需要用到dp[0][0]。综上所述,这个状态转移方程的边界为:dp[0][v] ( 0 ≤ v ≤ V 0 \le v \le V 0≤v≤V)。

边界的含义是从前0件物品中选若干件装入容量为v的背包中所能获得的最大价值,根据这个意义应该有dp[0][v] = 0。换个角度考虑,整个状态转移方程是从此边界开始的,因此dp[0][v]应该等于dp数组所能取到的最小值,而这个最小值恰好是0。

最优方案数

对于最优方案数,令cnt[i][v]表示从前i件物品中选择若干件装入容量为v的背包中,可以使得背包中物品价值最大的装入方案数。其状态转移方程为:
cnt[i][v] = { cnt[i-1][v] , if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i] cnt[i-1][v-w[i]] , if v ≥ w[i] and dp[i-1][v] > dp[i-1][v-w[i]] + c[i] cnt[i-1][v]+cnt[i-1][v-w[i]] , if v ≥ w[i] and dp[i-1][v] == dp[i-1][v-w[i]] + c[i] \text{cnt[i][v]}=\left\{ \begin{aligned} &\text{cnt[i-1][v]}, \quad\quad\quad \text{if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]} \\ &\text{cnt[i-1][v-w[i]]},\quad \text{if v$\ge$w[i] and dp[i-1][v] > dp[i-1][v-w[i]] + c[i]} \\ &\text{cnt[i-1][v]+cnt[i-1][v-w[i]]},\quad \text{if v$\ge$w[i] and dp[i-1][v] == dp[i-1][v-w[i]] + c[i]} \end{aligned} \right. cnt[i][v]=⎩ ⎨ ⎧​​cnt[i-1][v],if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]cnt[i-1][v-w[i]],if v≥w[i] and dp[i-1][v] > dp[i-1][v-w[i]] + c[i]cnt[i-1][v]+cnt[i-1][v-w[i]],if v≥w[i] and dp[i-1][v] == dp[i-1][v-w[i]] + c[i]​
接下来确定上述状态转移方程的边界。对于cnt数组的第一维,计算cnt[1][v]时,需要用到cnt[0][v],同时发现无法通过状态转移方程计算cnt[0][v],因此cnt[0][v]( 0 ≤ v ≤ V 0\le v\le V 0≤v≤V)就是一组边界。对于cnt数组的第二维,计算cnt[i][0]时,需要用到cnt[i-1][0],通过递推可知最终需要用到cnt[0][0]。综上所述,这个状态转移方程的边界为:cnt[0][v]( 0 ≤ v ≤ V 0\le v\le V 0≤v≤V)。

dp数组的边界值不同,cnt数组的边界值不太容易通过边界的含义来确定,因为cnt[0][v]的含义是从前0件物品中选若干件装入容量为v的背包中,可以使得背包中物品价值最大的装入方案数。我们从cnt数组的取值范围来考虑,根据题意,最优解是一定存在的,只不过是存在几个最优解的问题。根据状态转移方程,在一定的条件下,有cnt[1][v]=cnt[0][v],显然cnt[1][v]的值至少是1,因此cnt[0][v]的值应该初始化为1。

源代码

在上面的讨论中,用于记录状态的dpcnt都是二维数组,整个算法的空间复杂度为O(nV)。我们可以通过滚动数组的方式,将空间复杂度减小到O(V),下面分别给出空间复杂度为O(nV)和O(V)的实现。

O(nV)空间复杂度

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int maxV = 1e3 + 5;
int dp[maxn][maxV];
int cnt[maxn][maxV];
int n, V;
int w[maxn], c[maxn];int main(){scanf("%d %d", &n, &V);for(int i = 1; i <= n; i++)scanf("%d", &w[i]);for(int i = 1; i <= n; i++)scanf("%d", &c[i]);//初始化dp和cnt数组for(int v = 0; v <= V; v++){dp[0][v] = 0;cnt[0][v] = 1;}for(int i = 1; i <= n; i++){for(int v = 0; v <= V; v++){dp[i][v] = dp[i - 1][v];cnt[i][v] = cnt[i - 1][v];if(v >= w[i] && dp[i][v] < dp[i - 1][v - w[i]] + c[i]){dp[i][v] = dp[i - 1][v - w[i]] + c[i];cnt[i][v] = cnt[i - 1][v - w[i]];}else if(v >= w[i] && dp[i][v] == dp[i - 1][v - w[i]] + c[i]){cnt[i][v] = (cnt[i][v] + cnt[i - 1][v - w[i]]) % 10007;}}}printf("%d %d", dp[n][V], cnt[n][V]);}

O(V)空间复杂度

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int maxV = 1e3 + 5;
int dp[maxV];
int cnt[maxV];
int n, V;
int w[maxn], c[maxn];int main(){scanf("%d %d", &n, &V);for(int i = 1; i <= n; i++)scanf("%d", &w[i]);for(int i = 1; i <= n; i++)scanf("%d", &c[i]);//初始化dp和cnt数组for(int v = 0; v <= V; v++){dp[v] = 0;cnt[v] = 1;}for(int i = 1; i <= n; i++){for(int v = V; v >= 0; v--){if(v >= w[i] && dp[v] < dp[v - w[i]] + c[i]){dp[v] = dp[v - w[i]] + c[i];cnt[v] = cnt[v - w[i]];}else if(v >= w[i] && dp[v] == dp[v - w[i]] + c[i]){cnt[v] = (cnt[v] + cnt[v - w[i]]) % 10007;}}}printf("%d %d", dp[V], cnt[V]);
}

参考

胡凡、曾磊《算法笔记》

动态规划问题中边界和边界值的确定相关推荐

  1. 动态规划问题中找零问题 --C语言实现

    一.前言 今天又上了一节算法设计与分析课,头疼,学了动态规划的思想解决最值问题,行了,不啰嗦了,直接上干货干吧!!! 二.内容 题目: 三.分析过程 符合动态规划问题最值问题,故用动态规划来求解. 1 ...

  2. c语言测试数据的边界,黑盒测试-边界值(示例代码)

    从数学的角度出发,光从字面上的意思就知道是临界值.大量测试实践经验表明,边界值是最容易出现错误的地方,也是我们测试的重点. 测试边界值时,一般测试边界值和正好超出边界值一个单位的值. 边界值分析法就是 ...

  3. Python-OpenCV 处理图像(五):图像中边界和轮廓检测

    关于边缘检测的基础来自于一个事实,即在边缘部分,像素值出现"跳跃"或者较大的变化.如果在此边缘部分求取一阶导数,就会看到极值的出现. 而在一阶导数为极值的地方,二阶导数为0,基于这 ...

  4. Python-OpenCV 处理图像(四)(五):图像直方图和反向投影 图像中边界和轮廓检测

    当我们想比较两张图片相似度的时候,可以使用这一节提到的技术 直方图对比 反向投影 关于这两种技术的原理可以参考我上面贴的链接,下面是示例的代码: 0x01. 绘制直方图 import cv2.cv a ...

  5. 动态规划——矩阵中的最短路径长度

    文章出处:极客时间<数据结构和算法之美>-作者:王争.该系列文章是本人的学习笔记. 题目 假设我们有一个 n 乘以 n 的矩阵 w[n][n].矩阵存储的都是正整数.棋子起始位置在左上角, ...

  6. 频率很高的笔试题--动态规划类型(中)

    动态规划解题算法 1.预测赢家 2.乘积最大子序列 3.丑数 4.跳跃游戏 5.最长连接序列 6.爬楼梯 7.接雨水 8.最长公共子序列 可以不连续的子序列 要求连续的子序列 要求输出最长公共子串 9 ...

  7. 算法基础:动态规划数组中滚动数组的使用

    这篇文章继续在前一篇文章的基础上介绍动态规划数组的优化方式.很多基础算法本来都是写给我家的小少年看的,结果发现后浪学习的速度远远超出我的想象,在一个周末用这篇文章来纪念一下吧. 目录 斐波那契数列 d ...

  8. C#,动态规划问题中基于单词搜索树(Trie Tree)的单词断句分词( Word Breaker)算法与源代码

    分词是自然语言处理的基础,分词准确度直接决定了后面的词性标注.句法分析.词向量以及文本分析的质量.英文语句使用空格将单词进行分隔,除了某些特定词,如how many,New York等外,大部分情况下 ...

  9. 动态规划-矩阵中的最短路径

    一.牛客 NC59 矩阵的最小路径和 给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和. 数 ...

最新文章

  1. h5有缓存css,taro H5配置 cdn css js 缓存 hash 配置
  2. Python使用matplotlib可视化排序的点图、点图表示数据的等级顺序、沿着水平轴对齐(Ordered Dot Plot)
  3. shell编程基础之基本文本工具集合
  4. WPF界面设计技巧(3)—实现不规则动画按钮
  5. 使用pyinstaller打包django3.2
  6. 【win32】vs2010的窗体程序Helloworld
  7. .NET框架之“小马过河”
  8. 社区发现SLPA算法
  9. 对于右侧文字过多会跑到左侧的问题
  10. MPU6050工作原理及STM32控制MPU6050
  11. 问题解决,心情不错:)
  12. java接口自动化测试
  13. python卸载指令_如何卸载python插件
  14. java和eova的关系_eova ,一套jfinal开发框架,方便学习与 Jsp/Servlet 262万源代码下载- www.pudn.com...
  15. Step ‘Publish JUnit test result report’ failed: No test report files were found问题解决
  16. 北大计算机应用基础考研,北大考研辅导班-2021北京大学622计算机应用基础考研经验...
  17. LED驱动程序的编写
  18. 对于导入UE4中的模型坐标原点不在物体中心的解决办法
  19. 深入理解GO语言:map结构原理和源码分析
  20. python使用pandas读取excel绘制柱状图,折线图,饼状图

热门文章

  1. 行程单识别易语言代码
  2. STC15W单片机防丢语音报警GPS北斗定位测距双机LORA无线手持可充电
  3. J2EE进阶之HTML 一
  4. VUE中使用http请求
  5. 国内人工智能在教育教学的应用汇总
  6. max31865模块RTD测温注意事项
  7. MURA120T3G
  8. php页面怎么改造mip,代码适配的网站如何进行mip改造
  9. (转载)解密 [Dekrypt24@tutanota.com].mkp 病毒
  10. python高级语言程序设计答案_高级语言程序设计(Python)_MOOC章节测试答案