有参加蓝桥杯的同学可以给博主点个关注,博主也在准备蓝桥杯,可以跟着博主的博客一起刷题。
蓝桥杯
简单DP
先讲一些基础的DP问题,后续会更新复杂DP。
非传统DP问题思考方式,全新的DP思考方式:从集合角度来分析DP问题—— 闫式DP分析法
例题
AcWing 2. 01背包问题
模板题
01背包问题:有 N N N个物品,和一个容量是 V V V的背包,每一个物品有两个属性:体积 V i V_{i} Vi ,价值 W i W_{i} Wi ,每件物品只能用一次,要么用0次,要么用1次,所以称为01背包,问题为在背包装得下的情况下我们能挑出来的最大物品价值是多少。
利用闫式DP法分析:
状态表示 f(i, j):
只从前
i
i
i 个物品中选并且总体积小于等于
j
j
j 的选法的集合,存的数是集合里面每一个选法的总价值的最大值
状态计算 f(i ,j):
f
(
i
,
j
)
=
M
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
−
1
,
j
−
f(i, j) = Max(f(i - 1, j), f(i - 1,j -
f(i,j)=Max(f(i−1,j),f(i−1,j−
V
i
V_{i}
Vi)
+
W
i
+ W_{i}
+Wi
)
)
)
二维(基础写法)
import java.util.Scanner;
public class Main {
static final int N = 1010;
static int[] v = new int[N];
static int[] w = new int[N];
static int[][] f = new int[N][N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];
// 需要判断一下 j有可能装不下第i个物品
if (j >= v[i]) f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
System.out.print(f[n][m]);
}
}
一维优化
f(i)
只用到了f(i - 1)
这一层,f(j)一定小于等于j
,我们可以用一维数组来优化代码
import java.util.Scanner;
public class Main {
static final int N = 1010;
static int[] v = new int[N];
static int[] w = new int[N];
static int[] f = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i++) {
/*for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j] → f[j] = f[j];变为一维数组本行代码变为恒等式 即可直接删掉
if (j >= v[i]) 判断可以去掉 设j循环开始的值为v[i] for (int j = v[i]; j <= m; j++)
f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]) → f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
上面这行直接变为一维数组是不行的 j - v[i]严格小于j 我们的j是从小到大枚举的
我们的j - v[i]在第i层已经被计算过了 f[j - v[i]]其实是第i层的j - v[i]
原式:f[i - 1][j - v[i]] + w[i] 直接变成一维:f[j - v[i]] + w[i] 是不对的
如何解决这个问题?把j循环变成从大到小枚举 for (int j = m; j >= v[i]; j--) 让j - v[i]晚一步更新
}*/
for (int j = m; j >= v[i]; j--) {
f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
}
}
System.out.print(f[m]);
}
}
AcWing 1015. 摘花生
只能向下和向右走 求摘到最大花生数
看样例:
输入样例:
2 → 几组数据
2 2 → 第一组数据的行列
1 1
3 4
2 3 → 第二组数据的行列
2 3 4
1 6 5
所以输出8和16。
利用闫式DP法分析:
import java.util.Scanner;
public class Main {
static final int N = 110;
static int[][] w = new int[N][N];
static int[][] f = new int[N][N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while (T-- != 0) {
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
w[i][j] = sc.nextInt();
f[i][j] = Math.max(f[i - 1][j] + w[i][j], f[i][j - 1] + w[i][j]);
}
}
System.out.println(f[n][m]);
}
}
}
AcWing 895. 最长上升子序列
模板题
import java.util.Scanner;
public class Main {
static final int N = 1010;
static int a[] = new int[N];
static int f[] = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for (int i = 1; i <= n; i++) a[i] = sc.nextInt();
for (int i = 1; i <= n; i++) {
f[i] = 1; // 倒数第二个数不存在 序列中只有1个数
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) f[i] = Math.max(f[i], f[j] + 1);
}
}
int res = 0;
for (int i = 1; i <= n; i++) res = Math.max(res, f[i]); // 遍历f数组取max
System.out.println(res);
}
}
第五届2014年蓝桥杯真题
AcWing 1212. 地宫取宝
JavaB组第9题
是上面两个题:摘花生和最长上升子序列模型的结合版,只能向下或者向右走,拿着的宝贝价值要递增。
初步枚举我们要枚举一个四维的状态,f(i, j, k, c),i 和 j 是坐标,k是个数,c是价值;然后还要计算我们的状态转移方程,总共要五重for循环。
利用闫式DP法分析:
需要初始化:① f(1, 1, 1, w(1, 1)) = 1(取第一个数)② f(1, 1, 0, -1) = 1(不取第一个数),f表示的是方案数,不取也是一种方案,宝物的价值是可以为0的,所以当手中最大宝物的价值为-1时才能保证可以随便取任意价值的宝物,因为数组下标没法存负值,所以我们将所有宝物的价值都+1即可。
import java.util.Scanner;
public class Main {
static final int N = 55, MOD = 1000000007;
static int[][] w = new int[N][N];
static int[][][][] f = new int[N][N][13][14];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int k = sc.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
w[i][j] = sc.nextInt();
w[i][j]++; // 数组下标没法存负值 所以每个宝物值都+1
}
}
// 初始化
f[1][1][1][w[1][1]] = 1; // 取第一个物品 是1种方案
f[1][1][0][0] = 1; // 不取第一个物品 也是1种方案
for (int i = 1; i <= n; i++) {
for (int j = 1; j<= m; j++) {
if (i == 1 && j == 1) continue; // 因为前面已经初始化 所以直接continue 这行加上可以让代码更加圆滑
for (int u = 0; u <= k; u++) { // 遍历物品个数 因为可以是0个物品 故从0开始遍历
for (int v = 0; v <= 13; v++) {
int val = f[i][j][u][v]; // 用val存值方便后续计算
val = (val + f[i - 1][j][u][v]) % MOD; // 两个数相加就要取模 三个数相加会爆int
val = (val + f[i][j - 1][u][v]) % MOD;
if (u > 0 && w[i][j] == v) { // 一定要保证最少有1个物品 物品的价值为c
for (int c = 0; c < v; c++) { // c'的遍历
val = (val + f[i - 1][j][u - 1][c]) % MOD;
val = (val + f[i][j - 1][u - 1][c]) % MOD;
}
}
f[i][j][u][v] = val; // 没有像C++那样的&引用 所以要把值赋回去
}
}
}
}
int res = 0;
for (int c = 0; c <= 13; c++) res = (res + f[n][m][k][c]) % MOD;
System.out.print(res);
}
}
AcWing 1214. 波动数列
JavaA组第10题
背包问题(组合模型)”简单“ 的变种😭,不愧是A组压轴题
经过推导我们得出结论,求: ( n − 1 ) (n - 1) (n−1) d 1 + ( n − 2 ) d_{1} + (n - 2) d1+(n−2) d 2 d_{2} d2 + . . . + + ... + +...+ d n − 1 d_{n-1} dn−1 与 S S S 模 n n n 的余数相同
最终得出答案: f ( i − 1 , f(i - 1, f(i−1, ( j − i ⋅ a ) (j - i·a) (j−i⋅a) % n ) n) n)
综上,利用闫式DP分析法分析:
初始化:f(0, 0) = 1,一项都不选,总和是0,模n余数是0,方案数为1。
import java.util.Scanner;
public class Main {
static final int N = 1010, MOD = 100000007;
static int[][] f = new int[N][N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int s = sc.nextInt();
int a = sc.nextInt();
int b = sc.nextInt();
f[0][0] = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < n; j++) {
f[i][j] = (f[i - 1][get_mod(j - a * i, n)] + f[i - 1][get_mod(j + b * i, n)]) % MOD;
}
}
System.out.print(f[n - 1][get_mod(s, n)]);
}
private static int get_mod(int a, int b) { // 求a除以b的正余数
return (a % b + b) % b;
}
}
有对代码不理解的地方可以在下方评论