引入案例
概念
- 动态规划 状态转移方程(递推式 )
- 多阶段决策问题:动态规划常常用于求解多阶段决策问题
- 动态规划求解的过程依赖于上一步的求解过程,这是与分治法最大的区别,他们的本质还是大问题化为小问题。
背包问题
背包问题 可以分为 01 背包与完全背包下面介绍01 背包
- 有一个背包,容量为4磅 , 现有如下物品
- 要求达到的目标为装入的背包的总价值最大,并且重量不超出
- 要求装入的物品不能重复
核心思路
参数说明 :
v[i]、w[i]
分别为第i个物品的价值和重量,C为背包的容量。v[i][j]
表示在前i个物品中能够装入容量为j的背包中的最大价值(就是横纵坐标交错值)
实施步骤 :
v[i][0]=v[0][j]=0
表示 填入表 第一行和第一列是0- 当
w[i]> j
时: --> 准备加入新增的商品的容量大于 当前背包的容量
v[i][j]=v[i-1][j]
,就直接使用上一个单元格的装入策略 - 当
j>=w[i]
时–> 准备加入的新增的商品的容量小于等于当前背包的容量
v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
装入的方式: v[i-1][j]
: 就是上一个单元格的装入的最大值v[i]
: 表示当前商品的价值v[i-1][j-w[i]]
: 装入i-1
商品(装之前的商品),到剩余空间j-w[i]
的最大值
图解
代码
上述问题 求解代码
package Algorithm;
/**
* @author LZH.create
* 分治法 --> 动态规划
*/
public class KnapaskProblem {
public static void main(String[] args) {
// TODO Auto-generated method stub
int [] w = {1,4,3} ; // 三件商品的质量
int[] val = {1500, 3000, 2000}; //物品的价值 这里val[i] 就是前面的v[i]
int m = 4; //背包的容量
int n = val.length; //物品的个数
//v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
int[][] v = new int[n+1][m+1] ; // 其中的 1 为 0 行
//为了记录放入商品的情况,我们定一个二维数组
int[][] path = new int[n+1][m+1];
//初始化第一行和第一列, 这里在本程序中,可以不去处理,因为默认就是0
for(int i = 0; i < v.length; i++) {
v[i][0] = 0;
}
for(int i=0; i < v[0].length; i++) {
v[0][i] = 0;
}
// 动态规划
for(int i = 1; i < v.length; i++) {
for(int j=1; j < v[0].length; j++) {
//公式
if(w[i-1]> j) { // 因为我们程序i 是从1开始的,因此原来公式中的 w[i] 修改成 w[i-1]
v[i][j]=v[i-1][j];
} else {
if(v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
//把当前的情况记录到path
path[i][j] = 1; // 把放过东西的记录
} else {
v[i][j] = v[i - 1][j];
}
}
}
}
//输出一下v 看看目前的情况
for(int i =0; i < v.length;i++) {
for(int j = 0; j < v[0].length;j++) {
System.out.print(v[i][j] + " ");
}
System.out.println();
}
System.out.println("============================");
int i = path.length - 1; //行的最大下标
int j = path[0].length - 1; //列的最大下标
while(i > 0 && j > 0 ) { //从path的最后开始找
if(path[i][j] == 1) {
System.out.printf("第%d个商品放入到背包\n", i);
j -= w[i-1]; //w[i-1] 从后面开始 调整背包容量
}
i--;
}
}
}
正整数的摆动序列 (蓝桥杯)
- 第十一届蓝桥杯模拟赛
问题描述
-
如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即
a[2i]<a[2i-1], a[2i+1]>a[2i]
。 -
小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
-
输入格式 :输入一行包含两个整数 m,n。
-
输出格式 : 输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
-
样例输入 3 4
-
样例输出 14
样例说明
以下是符合要求的摆动序列:
2 1 2
2 1 3
2 1 4
3 1 2
3 1 3
3 1 4
3 2 3
3 2 4
4 1 2
4 1 3
4 1 4
4 2 3
4 2 4
4 3 4
评测用例规模与约定
对于 20% 的评测用例,1 <= n, m <= 5;
对于 50% 的评测用例,1 <= n, m <= 10;
对于 80% 的评测用例,1 <= n, m <= 100;
对于所有评测用例,1 <= n, m <= 1000。
题解
参考亓大佬
的解题思路 👍
首先贴下根据亓大佬的解法改编的 Java
代码
package SimulationGame11;
import java.util.Scanner;
/**
* @author LZH.create
* Date : 2021.4.12 动态规划永远的神!
*/
public class wigglingInteger {
public static void main(String [] args) {
Scanner sc = new Scanner(System.in) ;
int m = sc.nextInt() ; // 数的长度(几位数)
int n = sc.nextInt() ; // 数的可选范围
int [][] dp = new int[1024][1024] ;
for(int i = 1 ; i<= n ;i++) {
dp[1][i] = 1 ; // 第一位 数可选的为 1
}
for(int i = 2 ; i <= m ;i++) {
for(int j = 1 ; j <= n ; j++) {
if((i&1) == 1) { // 异或运算 判断奇偶
int temp = 0 ;
for(int k = 1; k <= j-1 ;k++ ) { // 第i-1位时选择,数为1到j-1的和
temp = (temp + dp[i-1][k]) % 10000 ; // 本来可以运用 +=
}
dp[i][j] = temp ;
} else { // 偶数
int temp = 0 ;
for(int k = j + 1 ; k <= n ;k++) { //第i-1位时,数为j+1到n的和
temp = (temp + dp[i-1][k]) %10000 ;
}
dp[i][j] = temp ;
}
}
}
// 输出结果 结果在最后一行 (即为第m 位数有多少结果)
int ans = 0 ;
for(int i = 0 ; i <= n ;i++) {
ans += dp[m][i] ;
}
System.out.println(ans);
}
}
滚动数组
动态规划怎么能少了 滚动数组思想呢?
详见下回分享哈~
推荐
这位北大的大佬讲解的不错 👉跳转链接