带※的题目,代表值得二刷、三刷、多刷!
蓝桥杯C++ AB组辅导课提单(Java解答版)
一、数学与简单DP
※1205、买不到的数目(简单)(互质两数不能凑出的最大数)
类似于下面这道题:
如果知道数学结论很快就可以得出答案:
也可以通过打表方式找规律。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
long n = scan.nextLong();
long m = scan.nextLong();
System.out.println(n * m - n - m);
}
}
同理,回到本题,跟上面一样的代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
long n = scan.nextLong();
long m = scan.nextLong();
System.out.println(n * m - n - m);
}
}
数论的题目只能多积累,因为涉及的东西太多了。
1211、蚂蚁感冒(简单)
对于感冒的蚂蚁,不管它向哪边走,如果它的左边有向右走的蚂蚁,或者它的右边有向左走的蚂蚁,它们都可能被感染。所以cnt = left + right + 1(left代表,感染蚂蚁的左边向右走的蚂蚁数,right代表,感染蚂蚁的右边向左走的蚂蚁数,+1是代表感染蚂蚁本身)。
上面的情况只是普遍情况,还要讨论特殊情况,例如:感冒的蚂蚁向左走,但是它左边没有向右的蚂蚁,或者感冒的蚂蚁向右走,但是它右边的蚂蚁没有向左的蚂蚁,那么都不能感染。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] ants = new int[n];
for (int i = 0; i < n; i++) {
ants[i] = scan.nextInt();
}
// 如果第一个蚂蚁为负值,头朝左
int left = 0;
int right = 0;
for (int i = 1; i < n; i++) {
if (Math.abs(ants[i]) < Math.abs(ants[0])) {
// 在左边,考虑头朝右
if (ants[i] > 0) left++;
} else {
// 在右边,考虑头朝左
if (ants[i] < 0) right++;
}
}
// 考虑特殊情况
if (ants[0] < 0 && left == 0) System.out.println(1);
else if (ants[0] > 0 && right == 0) System.out.println(1);
else System.out.println(left + right + 1);
}
}
1216、饮料换购(简单)
直接模拟就行
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int ans = n;
while (n >= 3) {
// 剩下的瓶盖数
int left = n % 3;
// 换购
ans += n / 3;
n = n / 3 + left;
}
System.out.println(ans);
}
}
2、01背包问题(简单)
经典中的经典
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int v = scan.nextInt();
int[] vs = new int[n + 1];
int[] ws = new int[n + 1];
for (int i = 1; i <= n; i++) {
vs[i] = scan.nextInt();
ws[i] = scan.nextInt();
}
// n件物品,v容量的背包
int[][] dp = new int[n + 1][v + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= v; j++) {
if (j >= vs[i]) {
// 当前物品放得下
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - vs[i]] + ws[i]);
} else {
// 当前物品放不下
dp[i][j] = dp[i - 1][j];
}
}
}
System.out.println(dp[n][v]);
}
}
1015、摘花生(简单)
同样,经典中的经典!
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int t = scan.nextInt();
int[][] penuts;
int[][] dp;
while ((t--) > 0) {
int r = scan.nextInt();
int c = scan.nextInt();
penuts = new int[r][c];
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
penuts[i][j] = scan.nextInt();
}
}
// 只能向东向南->向右向下
dp = new int[r][c];
dp[0][0] = penuts[0][0];
// 初始化第一行
for (int i = 1; i < c; i++) {
dp[0][i] = dp[0][i - 1] + penuts[0][i];
}
// 初始化第一列
for (int i = 1; i < r; i++) {
dp[i][0] = dp[i - 1][0] + penuts[i][0];
}
for (int i = 1; i < r; i++) {
for (int j = 1; j < c; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + penuts[i][j];
}
}
System.out.println(dp[r - 1][c - 1]);
}
}
}
895、最长上升子序列(简单)
典中典!(可以看看LeetCode的这道题,描述的更详细,会告诉你什么叫做子序列)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scan.nextInt();
}
// dp[i] 以第i个数结尾的,严格递增的子序列的最大长度
// 子序列:可以删除中间的某些数,但不能改变数的相对位置
int[] dp = new int[n];
dp[0] = 1;
int ans = 1;
for (int i = 1; i < n; i++) {
dp[i] = 1;
// 遍历i之前的数(因为按照dp定义,必须以i结尾)
int max = 1;
for (int j = 0; j <= i; j++) {
if (nums[j] < nums[i]) {
// + 1是代表必须以nums[i]结尾
max = Math.max(dp[j] + 1, max);
}
}
dp[i] = max;
ans = Math.max(ans, dp[i]);
}
System.out.println(ans);
}
}
※1212、地宫取宝(中等)(DP)
属于是上面两道题的升级版,贴一个y总分析法:
为什么取a[i][j]这个物品,必须要求k==a[i][j]呢?
import java.util.Scanner;
public class Main {
static int MOD = 1000000007;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
int c = scan.nextInt();
int[][] map = new int[n + 1][m + 1];
int maxVal = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
map[i][j] = scan.nextInt();
// 由于宝贝价值可能为0,不方便判断是否拿宝贝,所以全部++
map[i][j]++;
// 求所有宝贝最大价值
maxVal = Math.max(maxVal, map[i][j]);
}
}
// 多了两个状态:手中宝贝数、手中物品最大价值,那就成了4维DP
int[][][][] dp = new int[n + 1][m + 1][c + 1][maxVal + 1];
// dp[i][j][cnt][val],在i,j位置,手中持有cnt件物品,其中最大价值为val的方案数
// 第一个位置有两种情况,拿、不拿
dp[1][1][0][0] = 1;
dp[1][1][1][map[1][1]] = 1;
// 枚举所有情况
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int cnt = 0; cnt <= c; cnt++) {
// 枚举拿cnt件物品
for (int k = 0; k <= maxVal; k++) {
// 枚举最大价值
// 不拿,它只能从左边或上边转移过来
// 写上加自身是因为dp[1][1][0][0]本身是有值的,并不是=0的
// 而又无法避免cnt必须从0开始遍历,所以只能是自加
dp[i][j][cnt][k] = (dp[i][j][cnt][k] + dp[i - 1][j][cnt][k]) % MOD;
dp[i][j][cnt][k] = (dp[i][j][cnt][k] + dp[i][j - 1][cnt][k]) % MOD;
// 拿商品,那cnt必须 >0,且最大价值k就是当前物品自身
if (cnt > 0 && k == map[i][j]) {
// 遍历所有之前可能的最大价值,都可能转移过来
for (int s = 0; s < map[i][j]; s++) {
dp[i][j][cnt][k] = (dp[i][j][cnt][k] + dp[i - 1][j][cnt - 1][s]) % MOD;
dp[i][j][cnt][k] = (dp[i][j][cnt][k] + dp[i][j - 1][cnt - 1][s]) % MOD;
}
}
}
}
}
}
// 最后统计所有拿了c个物品的,但物品的最大价值不确定的所有方案数
int res = 0;
for (int i = 1; i <= maxVal; i++) {
res = (res + dp[n][m][c][i]) % MOD;
}
System.out.println(res);
}
}
※1214、波动数列(中等)(DP)
DP永远的坎…连续两道DP,我心态崩了…
扩展知识:负余数转正余数
int getMod(int a, int b) {
return (a % b + b) % b;
}
最不好理解的就是这里的转换:x为任意整数,所以得出,第一个数s,与后面n-1个数根据a、b操作组成的序列和模n的余数相同。
上面的转移方程还不够,因为我们要找与第一个数s同余,且长度为n-1的方案数,所以对j + b(n-i)、j - a(n-i)都要对n取余,因为题目中的数据可能为负数,但是数组index没法为负,就需要把负余数转成正余数,就是用上面的方法。最终答案就是:f[n - 1][s % n],当然这里的s % n也得用正余数。
import java.util.Scanner;
public class Main {
static int MOD = 100000007;
static int getMod(int a, int b) {
return (a % b + b) % b;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int s = scan.nextInt();
int a = scan.nextInt();
int b = scan.nextInt();
int[][] dp = new int[n][n];
// dp[i][j]选了i个数,前 i 个 d 的和模 n 的余数为 j 的集合的数量
// 这个d可以是+a、-b
dp[0][0] = 1;
// 一个数没选,模n为0的方案数=1
for (int i = 1; i < n; i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = (dp[i - 1][getMod(j + b * (n - i), n)] + dp[i - 1][getMod(j - a * (n - i), n)]) % MOD;
}
}
System.out.println(dp[n - 1][getMod(s, n)]);
}
}
二、枚举、模拟与排序
这一讲主要是考察写代码的仔细问题,根据题目一步步模拟即可,先暂时放一放,后续再更新 (这类题目是非常值得练手的题目,可以帮助自己写代码的时候更加细心仔细)
1210、连号区间数(简单)
1236、递增三元组(中等)
1245、特别数的和(简单)
1204、错误票据(简单)
466、回文日期(简单)
787、归并排序(简单)
1219、移动距离(简单)
1229、日期问题(简单)
1231、航班时间(简单)
1241、外卖店优先级(简单)
788、逆序对的数量(简单)
1237、螺旋折线(中等)
三、树状数组与线段树
这一讲的练习,都放在了树状数组、差分专题中,详情可点击链接。这一讲主要需要掌握:树状数组使用、差分多维数组(矩阵)、前缀和多维数组(矩阵)