蓝桥杯
我的AcWing
题目及图片来自蓝桥杯C++ AB组辅导课
复杂DP(下)
非传统DP问题思考方式,全新的DP思考方式:从集合角度来分析DP问题——闫式DP分析法
例题
AcWing 1303. 斐波那契前 n 项和
矩阵乘法+快速幂
此题并非dp问题,是为了后面的蓝桥杯真题铺垫知识点。
矩阵乘法:
矩阵 A A A 显然是存在的。
那么我们如果想求 F n F_n Fn
则有: F n = F n − 1 ⋅ A F_n = F_{n-1}·A Fn=Fn−1⋅A
所以: F n = F 1 ⋅ A n − 1 F_n = F_1·A^{n-1} Fn=F1⋅An−1
矩阵乘法是有结合律的,所以可以用快速幂来求。
接下来构造我们本题的矩阵, S n S_n Sn 是本题要求的前 n n n 项和:
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static final int N = 3;
static int n, m;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); m = sc.nextInt();
long[] f1 = {1, 1, 1}; // f1 fn+1 S1
long[][] a = { // A
{0, 1, 0},
{1, 1, 1},
{0, 0, 1}
};
n--; // A^n-1
while (n > 0) { // 快速幂模板
if ((n & 1) == 1) mul(f1, f1, a); // res = res * a
mul(a, a, a); // a = a * a
n >>= 1;
}
System.out.println(f1[2]); // Sn
}
// 矩阵乘法
private static void mul(long[] c, long[] a, long[][] b) {
long[] temp = new long[N];
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
temp[i] = (temp[i] + a[j] * b[j][i]) % m;
System.arraycopy(temp, 0, c, 0, N); // 拷贝数组
}
private static void mul(long[][] c, long[][] a, long[][] b) {
long[][] temp = new long[N][N];
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
temp[i][j] = (temp[i][j] + a[i][k] * b[k][j]) % m;
System.arraycopy(temp, 0, c, 0, N); // 拷贝数组
}
}
第八届2017年蓝桥杯真题
完全背包问题
朴素做法
import java.util.Scanner;
public class Main {
static 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(), 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++)
for (int k = 0; k * v[i] <= j; k++)
f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
System.out.println(f[n][m]);
}
}
优化做法
import java.util.Scanner;
public class Main {
static 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(), 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];
if (j >= v[i]) f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]);
}
System.out.println(f[n][m]);
}
}
AcWing 1226. 包子凑数
JavaB组第8题
完全背包问题+数论
假设我们有 N N N 个蒸笼,每个蒸笼的包子分别为 A 1 , A 2 . . . A n A_1,A_2...A_n A1,A2...An,且他们的最大公约数是 d d d,且 d > 1 d>1 d>1,所有数都是 d d d 的倍数,如果一个数不能被 d d d 整数,则说明这个数也一定不能被 A 1 , A 2 . . . A n A_1,A_2...A_n A1,A2...An 凑出来,所以此时会有正无穷 I N F INF INF 个数凑不出来。
当 g c d ( A 1 , A 2 . . . A n ) = 1 gcd(A_1,A_2...A_n)=1 gcd(A1,A2...An)=1 时,不能凑出来的数一定是有限个,这是依据裴蜀定理,裴蜀定理是基于两个数 a , b a,b a,b 的定理,当 g c d ( a , b ) = 1 gcd(a,b) = 1 gcd(a,b)=1 时,最大不能表示的数是: ( a − 1 ) ( b − 1 ) − 1 (a-1)(b-1)-1 (a−1)(b−1)−1, 当有 N N N 个数时定理也同样适用,当数变多时,这个上界必然会更小,因为可选的数变多了,因为 1 ≤ N ≤ 100 1\leq N \leq 100 1≤N≤100,小于 100 100 100 中最大互质的两个数是 98 , 99 98,99 98,99,所以这里的上界我们取到 10000 10000 10000 即可。
此时这个问题就变成了完全背包问题,在 10000 10000 10000 以内有多少个数不能被组合出来。
闫式DP分析法
此时的状态转移方程: f ( i , j ) = f ( i − 1 , j ) ∣ f ( i − 1 , j − A i ) ∣ f ( i − 1 , j − 2 A i ) ∣ . . . ∣ f ( i − 1 , j ≤ 0 ) f(i,j)=f(i-1,j)|f(i-1,j-A_i)|f(i-1,j-2A_i)|...|f(i-1,j\leq0) f(i,j)=f(i−1,j)∣f(i−1,j−Ai)∣f(i−1,j−2Ai)∣...∣f(i−1,j≤0)
时间复杂度为 O ( N 3 ) O(N^3) O(N3),状态的数量是 N 2 N^2 N2 个,转移的数量是 N N N 个,显然是需要 优化一下的,一般的背包问题时间复杂度是 O ( N 2 ) O(N^2) O(N2)。
我们做一下 j j j 的变量变换: f ( i , j − A i ) = f ( i − 1 , j − A i ) ∣ f ( i − 1 , j − 2 A i ) ∣ f ( i − 1 , j − 3 A i ) ∣ . . . f(i,j-A_i)=f(i-1,j-A_i)|f(i-1,j-2A_i)|f(i-1,j-3A_i)|... f(i,j−Ai)=f(i−1,j−Ai)∣f(i−1,j−2Ai)∣f(i−1,j−3Ai)∣...
此时这个等式可以替换为第一个等式从第二项开始后面的所有项:
状态转移方程优化为: f ( i , j ) = f ( i − 1 , j ) ∣ f ( i , j − A i ) f(i,j)=f(i-1,j)|f(i,j-A_i) f(i,j)=f(i−1,j)∣f(i,j−Ai)
import java.util.Scanner;
public class Main {
static final int N = 110, M = 10010;
static int[] a = new int[N];
static boolean[][] f = new boolean[N][M];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int d = 0;
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
d = gcd(d, a[i]);
}
if (d != 1) System.out.println("INF"); // 最大公约数不为1 有无限多个数凑不出来
else {
f[0][0] = true; // 0件物品 体积为0时也是一种方案
for (int i = 1; i <= n; i++) {
for (int j = 0; j < M; j++) {
f[i][j] = f[i - 1][j]; // 第一项一定存在
if (j >= a[i]) f[i][j] |= f[i][j - a[i]]; // 第二项不一定存在 用或"|"
}
}
int res = 0;
for (int i = 0; i < M; i++) {
if (!f[n][i]) res++;
}
System.out.println(res);
}
}
private static int gcd(int a, int b) {
return b != 0 ? gcd(b, a % b) : a;
}
}
我们发现我们的方程第 i i i 维只依赖于第 i i i 和 i − 1 i-1 i−1 项,所以我们可以将数组优化成一维:
import java.util.Scanner;
public class Main {
static final int N = 110, M = 10010;
static int[] a = new int[N];
static boolean[] f = new boolean[M];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int d = 0;
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
d = gcd(d, a[i]);
}
if (d != 1) System.out.println("INF"); // 最大公约数不为1 有无限多个数凑不出来
else {
f[0] = true; // 0件物品 体积为0时也是一种方案
for (int i = 1; i <= n; i++) {
for (int j = a[i]; j < M; j++) {
f[j] |= f[j - a[i]];
}
}
int res = 0;
for (int i = 0; i < M; i++) {
if (!f[i]) res++;
}
System.out.println(res);
}
}
private static int gcd(int a, int b) {
return b != 0 ? gcd(b, a % b) : a;
}
}
第六届2015年蓝桥杯真题
AcWing 1217. 垒骰子
JavaB组第9题
DP+矩阵乘法+快速幂
我们要把 n n n 个骰子一个一个往上摞,骰子一共有6个面,本题是有 m m m 组互斥的数,假如互斥数是 1 , 2 1,2 1,2,那么骰子在垒时,上下两个紧贴的面就不能是 1 1 1 和 2 2 2,求有多少种不同垒骰子的方案数。
闫式DP分析法
当然这是没有去掉不合理的方案总数。
这题太难理解了,没有继续了。