【AcWing算法基础课】第五章 动态规划(未完待续)(1)

简介: 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且 总价值最大 。

课前温习

8dc679b764b3809c1e1561c9d57b212c_d303555596e145cdbfeba228e635c7e5.png


初识DP

dp问题的优化:在基本形式dp上作等价变形。

dp问题的解题方法:

73b9c85589b760119731115246b5e0fb_a9d481e89325475eae9cd6aee22e18ac.png

1)状态表示


集合

属性:最大值/最小值/数量。

2)状态计算


集合划分(不重不漏)

一、 背包问题

1、0-1背包问题

题目链接: 2. 01背包问题 - AcWing题库


1.1题目描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。


第 i 件物品的体积是 vi,价值是 wi。


求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且 总价值最大 。


输出最大价值。


输入格式


第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。


接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。


输出格式


输出一个整数,表示最大价值。


数据范围


0


0


输入样例:


4 5
1 2
2 4
3 4
4 5


输出样例:


8


1.2思路分析



1)状态表示


集合:所有选法。而且选法需要满足①只从前i件物品中选;②满足总体积<=j。

属性:f[i][j]表示满足条件集合中的最大价值。

2)状态计算


集合划分:分成两类①满足集合条件而且不选i;②满足集合条件而且选i。

计算:①f[i-1][j];②先将所有集合中去掉第i件物品,然后求所有集合中的最大价值即f[i-1][j-v[i]],最后再把第i个物品加上,得到最大价值f[i-1][j-v[i]]+w[i]。两者取最大即可求得f[i][j]的最大值,即 f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])。

1.3代码实现

#include <iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{   cin>>n>>m;
    for(int i=1;i<=n;i++){
      cin>>v[i]>>w[i];
  } 
  //i=0,0=<j<=m时:放入0件物品时最大价值都是0,不需要初始化 
  for(int i=1;i<=n;i++){
  for(int j=0;j<=m;j++){
  //第一种情况一定存在 
    f[i][j]=f[i-1][j];
  //第二种情况当j>=v[i],即背包可以放下第i件物品时情况才存在 
  if(j>=v[i])
    f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
  }
  }
  cout<<f[n][m];
  return 0;
}


代码优化 :计算数组f[i]这一层时,只用到了f[i-1]这一层,所以可以利用滚动数组(原理:两行的二维数组,交替使用来计算下面层的元素值)来实现。所以,f[j]=max(f[j],f[j-v[i]]+w[i])。


注意 :背包容量需要从大到小遍历,即背包容量需逆序遍历,因为计算下一层时,如果从小到大遍历,则排在前面的就会先被更新成下一层的值,然后就把当前值给覆盖了,影响到了排在它后面的元素的更新。


优化代码


#include <iostream>
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];
  return 0;
}


2、完全背包问题

题目链接: 3. 完全背包问题 - AcWing题库


2.1题目描述

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。


第 i 种物品的体积是 vi,价值是 wi。


求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且 总价值最大 。


输出最大价值。


输入格式


第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。


接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。


输出格式


输出一个整数,表示最大价值。


数据范围


0


0


输入样例:


4 5
1 2
2 4
3 4
4 5


输出样例:


10


2.2思路分析

d9efcb84a6bc86dc279c95eb28077182_50159682309a462a949a87b04c9493ad.png


1)状态表示


集合:所有选法。而且选法需要满足①只从前i件物品中选;②满足总体积<=j。

属性:f[i][j]表示满足条件集合中的最大价值。

2)状态计算


集合划分:按照第i个物品选几个来划分。

计算:①i个物品选0个:f[i-1][j];②i个物品选1~k个:先将所有集合中去掉k个第i件物品,然后求所有集合中的最大价值即f[i-1][j-k*v[i]],最后再把k个第i件物品加上,得到最大价值f[i-1][j-k*v[i]]+k*w[i]。将两种条件综合可得,f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])。

2.3代码实现

#include <iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][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=0;j<=m;j++){
    //物品i的个数不能超过背包只放物品i最多可以存放的个数 
    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];
  return 0;
}


代码优化 :




将原有递推式展开,f[i][j]除f[i-1][j]这一项,其余每一项都比f[i][j-v[i]]多w[i],所以,f[i][j]除f[i-1][j]之外的最大值也比f[i][j-v[i]]的最大值多w[i]。所以 f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])。


优化代码

dada5596787693d6d1fb3e80967d4072_ed447799337d41ad9a92e7fc8a156538.png

#include <iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][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=0;j<=m;j++){
    //第一种情况一定存在 
      f[i][j]=f[i-1][j];
      //第二种情况当j>=v[i],即背包可以放下第i件物品时情况才存在 
      if(j>=v[i])
    f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
  }
  }
  cout<<f[n][m];
  return 0;
}


再次优化 :与0-1背包问题转一维类似,但是背包容量要正序遍历,原因:要求f[i][j]需知道该层的f[i][j- v[i]],因为f[i][j-v[i]]在f[i][j]之前,所以需要先更新f[i][j-v[i]],然后再更新f[i][j],也就是说需要从前往后遍历背包容量。


#include <iostream>
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=v[i];j<=m;j++){
    f[j]=max(f[j],f[j-v[i]]+w[i]);
  }
  }
  cout<<f[m];
  return 0;
}


3、多重背包问题 1

题目链接: 4. 多重背包问题 I - AcWing题库


3.1题目描述

有 N 种物品和一个容量是 V 的背包。


第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。


求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且 价值总和最大 。


输出最大价值。


输入格式


第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。


接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。


输出格式


输出一个整数,表示最大价值。


数据范围


0


0


输入样例:


4 5
1 2 3
2 4 1
3 4 3
4 5 2


输出样例:

10


3.2思路分析

a47cfd44853986554cd7e7e31c19117e_61df3860e3f845f78ec42d9a6236e854.png


注:本题数据数据范围较小,可以完全利用完全背包的朴素思路来过掉。


1)状态表示


集合:所有选法。而且选法需要满足①只从前i件物品中选;②满足总体积<=j。

属性:f[i][j]表示满足条件集合中的最大价值。

2)状态计算


集合划分:按照第i个物品选几个来划分。

计算:①i个物品选0个:f[i-1][j];②i个物品选1~k(k最大为s[i])个:先将所有集合中去掉k个第i件物品,然后求所有集合中的最大价值即f[i-1][j-k*v[i]],最后再把k个第i件物品加上,得到最大价值f[i-1][j-k*v[i]]+k*w[i]。将两种条件综合可得,f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])。

3.3代码实现

朴素二维

#include <iostream>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N];
int f[N][N];
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++){
    //和完全背包的条件类似:物品i的个数不能超过背包只放物品i最多可以存放的个数而且不能超过物品i的最大个数 
    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]);
    }
  }
  }
  cout<<f[n][m];
  return 0;
}


优化一维


#include <iostream>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N];
int f[N];
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=m;j>=v[i];j--){
    //和完全背包的条件类似:物品i的个数不能超过背包只放物品i最多可以存放的个数而且不能超过物品i的最大个数 
    for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
    f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
    }
  }
  }
  cout<<f[m];
  return 0;
}


4、多重背包问题 2

题目链接: 5. 多重背包问题 II - AcWing题库


4.1题目描述

有 N 种物品和一个容量是 V 的背包。


第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。


求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且 价值总和最大 。


输出最大价值。


输入格式


第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。


接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。


输出格式


输出一个整数,表示最大价值。


数据范围


0


0


0


提示:


本题考查多重背包的二进制优化方法。


输入样例:


4 5
1 2 3
2 4 1
3 4 3
4 5 2


4.2思路分析

6c8fb5b3aca930850353c35ce3b137fd_17c9b090bdad4cc69b469caecc31ae50.png


无法依据完全背包优化方式进行优化 ,原因:因为完全背包问题中,每个物品的数量是无限的,即


f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2v[i]]+2w[i],…,f[i - 1][j - k * v[i]] + k * w[i],...);


f[i][j - v[i]] = max( f[i-1][j-v[i]] ,f[i-1][j-2v[i]]+w[i],…,f[i - 1][j - k * v[i]] + (k - 1) * w[i],...);


因为是无限项,所以第一个式子f[i][j]除去第一项f[i-1][j]后和第二个式子项数是完全相等的,而每一项仅仅相差一个w[i],所以f[i][j-v[i]]+w[i]的最大值和f[i][j]去掉第一项后之后所有项的最大值相等,也就是说 f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])。


然而多重背包所放的每件物品都是有最大数量限制的,所以如上图,f[i][j]除去第一项后剩下的项数与f[i][j-v[i]]的项数不同,多了一个f[i][j-v[i]]的最后一项,所以不能像完全背包一样,像上面那样的优化。


优化方式(二进制优化):将第i个物品分组打包,针对 每组可以选也可以不选 ,即 0-1背包问题 ,针对某一个s[i]

e2b12e924b7ef3fdf977da8f3811d54b_6d45b909fa224bfc83ad6c5370bfff00.png



每次打包个数分别为:20,21,22,…,2k,而最后的 C<2(k+1)。

5b92f1b4c6e196062805da947b78e1a2_d27cc00f5e9a4bca972a5f5aeb6e0815.png



从1到2k可以凑出0 ~ 2~~(k+1)~~-1个第i个物品,在此基础上加上C之后可以拼出C ~ 2(k+1)-1+C个第i个物品,即[c~s]


36a0855b1e4433edd367bf6e2a78f1b8_18724fafc8044ba8984da89219529a48.png


由于C<2(k+1),所以两个区间的并集就是 [0,S]。


所以可以总结出,我们可以将s[i]分组为 logS[i]个,可以将时间复杂度从O(NVS)降到O(NVlogS[i])。


4.3代码实现

#include <iostream>
using namespace std;
const int N=25000,M=2000;
int n,m;
int v[N],w[N];
int f[N];
int main()
{   cin>>n>>m;
    int cnt=0;   //打包的组号 
    for(int i=1;i<=n;i++){
      int a,b,s;//当前物品的体积、价值、个数 
      cin>>a>>b>>s; 
      int k=1;
      //打包过程 
      //k<=s时可以分组打包 
      while(k<=s){
      cnt++;
      v[cnt]=a*k;
      w[cnt]=b*k;
      s-=k;  //每次分完一次后,物品总数减去k 
      k*=2;  //每次分组,物品个数为2的幂
  }
  //如果没有正好分完,将剩余物品打包成一组。 
  if(s>0){
    cnt++;
    v[cnt]=a*s;
    w[cnt]=b*s;
  } 
  } 
  n=cnt; //分组数即为物品数,每组有不同个物品,可选可不选,转化成0-1背包 
  //0-1背包 
  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];
  return 0;
}


5、分组背包问题

题目链接: 9. 分组背包问题 - AcWing题库


5.1题目描述

有 N 组物品和一个容量是 V 的背包。


每组物品有若干个,同一组内的物品最多只能选一个。


每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。


求解将哪些物品装入背包,可使物品总体积不超过背包容量,且 总价值最大。


输出最大价值。


输入格式


第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。


接下来有 N 组数据:


每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;

每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

输出格式


输出一个整数,表示最大价值。


数据范围


0


0


0


输入样例:


3 5
2
1 2
2 4
1
3 4
1
4 5


输出样例:

8


5.2思路分析

2f6728f2c8efedf4c050bc0455732c84_afee8ec3b6fe47c6afc57443f089a68c.png


1)状态表示


集合:所有选法。而且选法需要满足①只从前i件物品中选;②满足总体积<=j。

属性:f[i][j]表示满足条件集合中的最大价值。

2)状态计算


集合划分:按照第i组物品选几个来划分。

计算:①第i组物品选0个:f[i-1][j];②第i组物品选第k个:f[i-1][j-v[i][k]]+w[i][k]。将两种条件综合可得,f[i][j]=max(f[i-1][j],f[i-1][j-v[i][k]]+w[i][k])。

注 :遍历顺序:如果递推需要由上一层推得,则需要逆序遍历背包容量;如果递推需要由本层推得,则需要正序遍历背包容量。

可以类似优化为一维:f[j]=max(f[j],f[j-v[i][k]]+w[i][k])。

5.3代码实现

#include <iostream>
using namespace std;
const int N=110;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N];
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>=0;j--){
      for(int k=1;k<=s[i];k++){
        // f[j-v[i][k]]存在才判断是否更新最大,若不存在,f[j]=f[j] 
        if(v[i][k]<=j){
        f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
    }
    } 
  }
  }
  cout<<f[m];
  return 0;
}


目录
相关文章
|
1月前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
56 8
|
1月前
|
算法 Python
算法不再难!Python分治法、贪心、动态规划实战解析,轻松应对各种算法挑战!
【7月更文挑战第8天】掌握Python算法三剑客:分治、贪心、动态规划。分治如归并排序,将大问题拆解递归解决;贪心策略在每步选最优解,如高效找零;动态规划利用子问题解,避免重复计算,解决最长公共子序列问题。实例展示,助你轻松驾驭算法!**
43 3
|
5天前
|
机器学习/深度学习 算法 Java
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
|
5天前
|
算法 Java 测试技术
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
这篇文章介绍了基于动态规划法的三种算法:解决背包问题的递归和自底向上实现、Warshall算法和Floyd算法,并提供了它们的伪代码、Java源代码实现以及时间效率分析。
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
|
1月前
|
算法 Python
Python算法高手进阶指南:分治法、贪心算法、动态规划,掌握它们,算法难题迎刃而解!
【7月更文挑战第10天】探索Python算法的精华:分治法(如归并排序)、贪心策略(如找零钱问题)和动态规划(解复杂问题)。通过示例代码揭示它们如何优化问题解决,提升编程技能。掌握这些策略,攀登技术巅峰。
40 2
|
1月前
|
算法 程序员 Python
算法小白到大神的蜕变之路:Python分治法、贪心、动态规划,一步步带你走向算法巅峰!
【7月更文挑战第9天】探索算法之旅,以Python解锁编程高手之路。分治法如二分查找,将复杂问题拆解;贪心算法解决活动选择,每次选取局部最优;动态规划求斐波那契数列,避免重复计算,实现全局最优。每一步学习,都是编程能力的升华,助你应对复杂挑战,迈向算法大师!
29 1
|
1月前
|
存储 算法 Python
Python算法界的秘密武器:分治法巧解难题,贪心算法快速决策,动态规划优化未来!
【7月更文挑战第9天】Python中的分治、贪心和动态规划是三大关键算法。分治法将大问题分解为小问题求解,如归并排序;贪心算法每步选局部最优解,不保证全局最优,如找零钱;动态规划存储子问题解求全局最优,如斐波那契数列。选择合适算法能提升编程效率。
41 1
|
1月前
|
存储 算法 Python
震撼!Python算法设计与分析,分治法、贪心、动态规划...这些经典算法如何改变你的编程世界!
【7月更文挑战第9天】在Python的算法天地,分治、贪心、动态规划三巨头揭示了解题的智慧。分治如归并排序,将大问题拆解为小部分解决;贪心算法以局部最优求全局,如Prim的最小生成树;动态规划通过存储子问题解避免重复计算,如斐波那契数列。掌握这些,将重塑你的编程思维,点亮技术之路。
39 1
|
1月前
|
存储 算法 大数据
Python算法高手的必修课:深入理解分治法、贪心算法、动态规划,让你的代码更智能!
【7月更文挑战第9天】在Python算法学习中,分治法(如归并排序)将大问题分解为小部分递归解决;贪心算法(如货币找零)在每步选择局部最优解尝试达到全局最优;动态规划(如斐波那契数列)通过存储子问题解避免重复计算,解决重叠子问题。掌握这三种方法能提升代码效率,解决复杂问题。
32 1
|
1月前
|
算法 索引 Python
逆袭算法界!Python分治法、贪心算法、动态规划深度剖析,带你走出算法迷宫!
【7月更文挑战第8天】分治法,如快速排序,将大问题分解并合并解;贪心算法,选择局部最优解,如活动选择;动态规划,利用最优子结构避免重复计算,如斐波那契数列。Python示例展示这些算法如何解决实际问题,助你精通算法,勇闯迷宫。
23 1