0
点赞
收藏
分享

微信扫一扫

【算法练习】蓝桥杯C++ AB组辅导课题单:第三、四、五讲(Java解答版)

带※的题目,代表值得二刷、三刷、多刷!

蓝桥杯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、螺旋折线(中等)


三、树状数组与线段树

这一讲的练习,都放在了树状数组、差分专题中,详情可点击链接。这一讲主要需要掌握:树状数组使用、差分多维数组(矩阵)、前缀和多维数组(矩阵)

1264、动态求连续区间和(简单)

1265、数星星(中等)

1270、数列区间最大值(简单)

1215、小朋友排队(简单)

1228、油漆面积(困难)

※※1232、三体攻击(困难)

797、差分(简单)

※798、差分矩阵(简单)

举报

相关推荐

0 条评论