【算法练习】蓝桥杯C++ AB组辅导课题单:第一、二讲(Java解答版)(已获Java-B组国二)

反正听y哥说刷完肯定进国赛,我就试试~

感谢y总,进国赛了,并且拿到了JavaB组国二,同学们照着我博客里的方法刷,拿国奖真的很简单!

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

一、递归与递推

92. 递归实现指数型枚举(简单)

在这里插入图片描述
注意本题是求解组合!!

import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static LinkedList<Integer> tmp = new LinkedList<>();
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		dfs(n, 1);
	}
	static void dfs(int n, int start) {
		if (tmp.size() == 0) System.out.println();
		if (tmp.size() > 0) {
			for (int i = 0; i < tmp.size(); i++) {
				System.out.printf("%d ", tmp.get(i));
			}
			System.out.println();
		}
		if (tmp.size() > n) return;
		for (int i = start; i <= n; i++) {
			tmp.add(i);
			dfs(n, i + 1);
			// 回溯
			tmp.removeLast();
		}
	}
}
94. 递归实现排列型枚举(简单)

在这里插入图片描述
注意是求排列,要使用vis数组,在输出结果时注意使用BufferedWriter节省时间。

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
	static LinkedList<Integer> tmp = new LinkedList<>();
	static boolean[] vis;
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		vis = new boolean[n + 1];
		dfs(n);
		// BufferedWriter用完,一定要flush,不然不会输出
		log.flush();
	}
	static void dfs(int n) throws IOException {
		if (tmp.size() == n) {
			for (int i = 0; i < tmp.size(); i++) {
				log.write(tmp.get(i) + " ");
			}
			log.write("\n");
		}
		if (tmp.size() > n) return;
		for (int i = 1; i <= n; i++) {
			if (vis[i]) continue;
			tmp.add(i);
			vis[i] = true;
			dfs(n);
			// 回溯
			vis[i] = false;
			tmp.removeLast();
		}
	}
}
717. 简单斐波那契(中等)

在这里插入图片描述

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int[] f = new int[47];
		f[0] = 0;
		f[1] = 1;
		f[2] = 1;
		for (int i = 3; i < n; i++) {
		    f[i] = f[i - 1] + f[i - 2];
		}
		for (int i = 0; i < n; i++) {
			System.out.printf("%d ", f[i]);
		}
	}
}
※95、费解的开关(中等)(状态压缩—枚举)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
先说结论:第一行的状态确定后,第二行的状态也肯定确定(就是去按动第一行为0的正下方的一个按钮),以此类推,第三四行也确定,我们只需要判断最后一行是否全1,就能确定是否全部点亮,因为前面的肯定被点亮。

枚举第一行的状态,可以通过状态压缩,用二进制串来枚举,一行5个开关,每个开关有两个状态,共 2 ^ 5 = 32种状态,00000代表全不按动,11111代表全按一遍(注意这里的0和1与题目中不一样,这里的0和1是用来枚举第一行的多个状态的)

还要注意的是数组的拷贝问题!!!
在这里插入图片描述

import java.util.Arrays;
import java.util.Scanner;

public class Main {
	// 包括自身点在内的5个方向
	static int[] x = new int[] {1,-1,0,0,0};
	static int[] y = new int[] {0,0,1,-1,0};
	static char[][] map;
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		map = new char[5][5];
		while ((n--) > 0) {
			for (int i = 0; i < 5; i++) {
				map[i] = scan.next().toCharArray();
			}
			// 备份地图
			char[][] backUp = new char[5][5];
			for (int i = 0; i < 5; i++) {
				backUp[i] = Arrays.copyOf(map[i], 5);
			}
			// 全局结果
			int ans = 7;
			// 遍历第一行的所有操作可能 2^5
			for (int i = 0; i < (1 << 5); i++) {
				// 遍历当前第一行状态下的最小操作数
				int count = 0;
				// 5位数,看哪一位为1,就按动该位开关
				for (int j = 0; j < 5; j++) {
					if (((i >> j) & 1) == 1) {
						// 按动第1行的按钮
						turn(0, j);
						count++;
					}
				}
				// 开始遍历后面行,最后一行用于判定
				for (int j = 0; j < 4; j++) {
					for (int k = 0; k < 5; k++) {
						if (map[j][k] == '0') {
							// 第一行有灯没亮,第一行状态已经确定了
							// 必须按动正下面的灯
							turn(j + 1, k);
							count++;
						}
					}
				}
				// check最后一行是否全1
				boolean flag = false;
				for (int j = 0; j < 5; j++) {
					if (map[4][j] == '0') {
						flag = true;
						break;
					}
				}
				// 更新全局答案
				if (flag == false) {
					ans = Math.min(ans, count);
				}
				// 复原地图
				// 一定要通过copy方式来复原和保存地图,不要直接=变量名
				for (int j = 0; j < 5; j++) {
					map[j] = Arrays.copyOf(backUp[j], 5);
				}
			}
			if (ans > 6) System.out.println(-1);
			else System.out.println(ans);
		}
	}
	// 按动开关
	static void turn(int i, int j) {
		for (int k = 0; k < 5; k++) {
			int tx = i + x[k];
			int ty = j + y[k];
			if (tx < 0 || ty < 0 || tx >= 5 || ty >= 5) continue;
			// 异或
			map[tx][ty] ^= 1;
		}
	}
}

上面这个题不要想当然的用搜索做,没有那么复杂,就是用状态压缩,而且只需要遍历第一行的状态就可以。

再贴一道LeetCode的状态压缩题目

6029、射箭比赛中的最大得分(中等)

在这里插入图片描述
在这里插入图片描述
考虑Bob对于0-11个Scoring Section的情况,可以赢、不赢,遍历所有可能子集即可!

class Solution {
    public int[] maximumBobPoints(int numArrows, int[] aliceArrows) {
    	int m = aliceArrows.length;
    	int maxScore = 0;
    	int[] ans = new int[m];
    	for (int i = 1; i < (1 << m); i++) {
    		// 枚举所有组合情况,即:0-11场,全不赢-全赢的所有子集(全不赢排除掉)
    		int need = 0;
    		int score = 0;
    		for (int j = 0; j < m; j++) {
    			if ((i & (1 << j)) != 0) {
    				// 说明这场赢了,只需要比alice多射一次
    				need += aliceArrows[j] + 1;
    				score += j;  // 统计获分情况
    			}
    		}
    		if (need > numArrows) {
    			// 需要的箭多于拥有的,当前方案不成立
    			continue;
    		}
    		// 找最大值
    		if (score > maxScore) {
    			maxScore = score;
    			for (int j = 0; j < m; j++) {
    				if ((i & (1 << j)) != 0) {
    					ans[j] = aliceArrows[j] + 1;
    				} else {
                        ans[j] = 0;
                    }
    			}
    			// 剩余的箭随便放一个地方就行
    			ans[0] += numArrows - need;
    		}
    	}
    	return ans;
    }
}
93、递归实现组合型枚举(简单)

在这里插入图片描述
正常dfs,不用vis记录,注意下数组长度即可。

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
	static LinkedList<Integer> ans = new LinkedList<>();
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int m = scan.nextInt();
		dfs(n, m, 1);
		// BufferdWriter一定flush
		log.flush();
	}
	static void dfs(int n, int m, int start) throws IOException {
		if (ans.size() == m) {
			for (int num : ans) {
				log.write(num + " ");
			}
			log.write("\n");
			return;
		}
		if (ans.size() > m) return;
		for (int i = start; i <= n; i++) {
			ans.add(i);
			dfs(n, m, i + 1);
			// 回溯
			ans.removeLast();
		}
	}
}
※1209、带分数(简单)(DFS搜索+暴力枚举)

在这里插入图片描述

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static LinkedList<Integer> tmp = new LinkedList<>();
	static boolean[] vis = new boolean[10];
	static int n;
	static int ans = 0;
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		dfs();
		System.out.println(ans);
	}
	static void dfs() {
		if (tmp.size() > 9) return;
		// 9个数的排列
		if (tmp.size() == 9) {
			// n = a + b/c, a只可能1、2位数
			// n * c = a * c + b
			// 遍历枚举三个数
			int a = 0;
			for (int i = 0; i < 9 - 2; i++) {
				a = a * 10 + tmp.get(i);
				if (a >= n) continue;
				int b = 0;
				for (int j = i + 1; j < 9 - 1; j++) {
					b = b * 10 + tmp.get(j);
					int c = 0;
					for (int k = j + 1; k < 9; k++) {
						c = c * 10 + tmp.get(k);
						// 满足题目条件,且用完了所有数
						if (n * c == a * c + b && k == 8) {
							ans++;
							break;
						}
					}
				}
			}
			return;
		}
		for (int i = 1; i <= 9; i++) {
			if (vis[i]) continue;
			vis[i] = true;
			tmp.add(i);
			dfs();
			vis[i] = false;
			tmp.removeLast();
		}
	}
}

之前刷到这题一下子就秒了,现在遇到还是思考了一会…真是每个阶段自己擅长的题目都不一样…(脑容量属实有限)

※116、飞行员兄弟(简单)(DFS搜索)(区别于第95题)

在这里插入图片描述
在这里插入图片描述
注意这道题不能用状态压缩+枚举来做,因为按动开关会影响一整行和一整列,不再是上下左右!!

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

class node {
	int x, y;
	node(){};
	node(int x, int y) {
		this.x = x;
		this.y = y;
	}
}
public class Main {
	static char[][] map;
	static boolean[][] vis;
	static LinkedList<node> ansNodes = new LinkedList<>();
	static int ans = Integer.MAX_VALUE;
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		map = new char[4][4];
		vis = new boolean[4][4];
		for (int i = 0; i < 4; i++) {
			map[i] = scan.next().toCharArray();
		}
		dfs(0, 0, 0, new LinkedList<>());
		System.out.println(ans);
		for (node t : ansNodes) {
			System.out.println(t.x + " " + t.y);
		}
	}
	static void dfs(int x, int y, int cnt, LinkedList<node> tmp) {
		if (y == 4) {
			x++;
			y = 0;
		}
		if (x == 4) {
			boolean flag = true;
			for (int i = 0; i < 4; i++) {
				for (int j = 0; j < 4; j++) {
					if (map[i][j] == '+') {
						flag = false;
						break;
					}
				}
				if (flag == false) break;
			}
			if (flag) {
				if (cnt < ans) {
					ans = cnt;
					ansNodes = new LinkedList<>(tmp);
				}
			}
			return;
		}
		if (vis[x][y]) return;
		// 按动当前开关和其行、列开关
		turn(x, y);
		vis[x][y] = true;
		// 记录坐标
		tmp.add(new node(x + 1, y + 1));
		dfs(x, y + 1, cnt + 1, new LinkedList<>(tmp));
		// 回溯
		turn(x, y);
		vis[x][y] = false;
		tmp.removeLast();
		dfs(x, y + 1, cnt, new LinkedList<>(tmp));
	}
	static void turn(int x, int y) {
		for (int i = 0; i < 4; i++) {
			if (map[x][i] == '+') map[x][i] = '-';
			else map[x][i] = '+';
			if (map[i][y] == '+') map[i][y] = '-';
			else map[i][y] = '+';
		}
		if (map[x][y] == '+') map[x][y] = '-';
		else map[x][y] = '+';
	}
}
1208、翻硬币(简单)(贪心)

在这里插入图片描述
刚开始还想着DFS、状态压缩,发现直接贪心就可以了。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		String origin = scan.next();
		String dest = scan.next();
		char[] org = origin.toCharArray();
		char[] des = dest.toCharArray();
		// 贪心,比较每一个硬币,不同就翻
		int count = 0;
		for (int i = 0; i < org.length - 1; i++) {
			if (org[i] == des[i]) continue;
			count++;
			org[i] = org[i] == '*' ? 'o' : '*';
			org[i + 1] = org[i + 1] == '*' ? 'o' : '*';
		}
		System.out.println(count);
	}
}

二、二分与前缀和

※789、数的范围(简单)(普通二分模板)

在这里插入图片描述
是一道很好的回顾二分模板的题目,嗯~,我就错了个细节。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static int[] nums;
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int q = scan.nextInt();
		nums = new int[n];
		for (int i = 0; i < n; i++) {
			nums[i] = scan.nextInt();
		}
		while ((q--) > 0) {
			int key = scan.nextInt();
			// 先找起始位置
			int a = binarySearch1(key);
			if (a == -1) {
				// 起始都没有,最后肯定也没有
				System.out.printf("-1 -1\n");
			} else {
				int b = binarySearch2(key);
				System.out.printf("%d %d\n", a, b - 1);
			}
		}
	}
	// 记录第一次出现的位置
	public static int binarySearch1(int key) {
		int left = 0;
		int right = nums.length - 1;
		int ans = -1;
		int mid;
		while (left <= right) {
			mid = left + (right - left) / 2;
			if (nums[mid] == key) {
				ans = mid;
				// 继续往左边找第一个出现的下标
				right = mid - 1;
			} else if (nums[mid] < key) {
				left = mid + 1;
			} else {
				right = mid - 1;
			}
		}
		return ans;
	}
	// 记录最后出现的位置(找最后大于key的位置)
	public static int binarySearch2(int key) {
		int left = 0;
		int right = nums.length;
		int mid;
		while (left < right) {
			mid = left + (right - left) / 2;
			if (nums[mid] > key) {
				right = mid;
			} else {
				left = mid + 1;
			}
		}
		// 如果没有找到,那就返回数组长度
		return left;
	}
}

※790、数的三次方(简单)(浮点数二分模板)

在这里插入图片描述
浮点数二分模板,left就是题目给出的数据范围,right也是题目给出的数据范围,while的判断条件变为right - left的精度eps。每次更新直接left = mid,或者right = mid。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		double n = scan.nextDouble();
		double eps = 0.0000001;
		double left = -10000;
		double right = 10000;
		double mid;
		while (right - left > eps) {
			mid = left + (right - left) / 2;
			if (mid * mid * mid <= n) {
				left = mid;
			} else {
				right = mid;
			}
		}
		System.out.printf("%.6f", left);
	}
}
795、前缀和(简单)(一维前缀和)

在这里插入图片描述

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int m = scan.nextInt();
		int[] nums = new int[n];
		// 前缀和数组
		int[] preSum = new int[n + 1];
		for (int i = 0; i < n; i++) {
			nums[i] = scan.nextInt();
			preSum[i + 1] = nums[i] + preSum[i];
		}
		while ((m--) > 0) {
			int l = scan.nextInt();
			int r = scan.nextInt();
			System.out.println(preSum[r] - preSum[l - 1]);
		}
	}
}
※796、子矩阵的和(简单)(二维前缀和)

一维前缀和好理解,二维就不咋好理解了,定义:以(1, 1)为左上角以(i, j)为右下角这个矩阵里面数的和。
在这里插入图片描述
假设我们知道了每个矩阵的和,以上面这个图为例,我们想要求黑色部分的和,D的坐标是(i, j),ABCD边长为1(意味着黑色矩阵只代表一个数),试试看,如何进行转换?

=f[i][j] - f[i - 1][j] - f[i][j - 1] + f[i - 1]f[j - 1]

是不是很简单,理解二维前缀和,最好记住上面这幅图。

那每个矩阵的前缀和如何求?

同样利用上面的图,现在假设ABCD那个黑色块代表一个数,现在要求f[i][j]:
=f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + ABCD的数字值

好咯 ~ 咱们来做题 ~


输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。

对于每个询问输出子矩阵中所有数的和。

输入格式
第一行包含三个整数n,m,q。

接下来n行,每行包含m个整数,表示整数矩阵。

接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。

输出格式
共q行,每行输出一个询问的结果。

数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21


需要注意题目中是否包含左上角右下角坐标点,包含的话会有略微调整,具体看代码:

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int m = scan.nextInt();
		int q = scan.nextInt();
		int[][] map = new int[n + 1][m + 1];
		// 前缀和数组
		int[][] preSum = new int[n + 1][m + 1];
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				map[i][j] = scan.nextInt();
				// 和一维的一样,可以边读取边计算
				preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + map[i][j];
			}
		}
		while ((q--) > 0) {
			int x1 = scan.nextInt();
			int y1 = scan.nextInt();
			int x2 = scan.nextInt();
			int y2 = scan.nextInt();
			// 忘记公式不要怕,回想那幅图
			// 这里用[x1-1][y2] 和 [x2][y1-1]是因为[x1][y1]的那个点是算在内的
			System.out.println(preSum[x2][y2] - preSum[x1 - 1][y2] - preSum[x2][y1 - 1] + preSum[x1 - 1][y1 - 1]);
		}
	}
}

天上的星星

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
是标准的二维前缀和问题,一定要注意坐标是直角坐标系还是数组坐标系,数组坐标系的(0,0)点在左上角,而直角坐标系的(0,0)点在左下角,所以二位前缀和的构造形式也有区别。还需要注意是否包含边界点,包含的话需要对某些坐标-1。

import java.util.*;
import java.io.*;

public class Main {
	static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
	public static void main(String[] args) throws IOException {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int[][] star = new int[2002][2002];
		for (int i = 0; i < n; i++) {
			int a = scanner.nextInt();
			int b = scanner.nextInt();
			int w = scanner.nextInt();
			star[a + 1][b + 1] += w;  // 1个点可能有多个星星
		}
		int[][] preSum = new int[2010][2010];
		for (int i = 1; i <= 2001; i++) {
			for (int j = 1; j <= 2001; j++) {
				preSum[i][j] = star[i][j] + preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1];
			}
		}
		int q = scanner.nextInt();
		while (q-- > 0) {
			int x1 = scanner.nextInt() + 1;
			int y1 = scanner.nextInt() + 1;
			int x2 = scanner.nextInt() + 1;
			int y2 = scanner.nextInt() + 1;
			// 注意建立的是直角坐标系
			// -1是因为矩阵要包含边界点(根据题目含义选择是否包括边界点)
			System.out.println(preSum[x2][y2] + preSum[x1 - 1][y1 - 1] - preSum[x1 - 1][y2] - preSum[x2][y1 - 1]);
		}
	}
}
730、机器人跳跃问题(中等)(二分搜索答案)

在这里插入图片描述
在这里插入图片描述
二分答案就行。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static int[] nums;
	static int n;
	static int max;
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		nums = new int[n];
		max = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			nums[i] = scan.nextInt();
			if (nums[i] > max) max = nums[i];
		}
		// 二分找答案
		int left = 0;
		int right = max;
		int mid;
		int ans = 0;
		while (left <= right) {
			mid = left + (right - left) / 2;
			if (check(mid)) {
				ans = mid;
				right = mid - 1;
			} else {
				left = mid + 1;
			}
		}
		System.out.println(ans);
	}
	// 看当前答案是否满足要求
	public static boolean check(int num) {
		int tmp = num;
		for (int i = 0; i < n; i++) {
			if (nums[i] > tmp) {
				tmp -= (nums[i] - tmp);
			} else {
				tmp += (tmp - nums[i]);
			}
			if (tmp < 0) return false;
			// 一旦大于当前数组中最大值,无论走到哪里都是加血
			// 这里一定要提前返回true,否则累加会超出int的范围,变成负数影响判断
			if (tmp >= max) return true;
		}
		return true;
	}
}
1221、四平方和定理(简单)(哈希表、n数之和)

在这里插入图片描述
当时耍蓝桥杯的系统,这道题三重循环暴力就过了,Acwing加强了数据暴力要超时,改用哈希表,先存储后两个数字,再去遍历前两个数字,注意要按照字典序,所以一旦哈希表中有数了,就不能再放了,第一次放入的数就是最优的。

这种解决方法常见于LeetCode的n数之和,在我的哈希表专题里面有具体讲解。

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		String[] strings = new String[5000000];
		for (int i = 0; i * i <= n; i++) {
			for (int j = i; i * i + j * j <= n; j++) {
				// 注意前面如果找到过了就不要找了,因为最开始找到的就是最优的
				if (strings[i * i + j * j] == null) {
					strings[i * i + j * j] = i + " " + j;
				}
			}
		}
		boolean flag = false;
		for (int i = 0; i * i <= n; i++) {
			for (int j = i; i * i + j * j <= n; j++) {
				if (strings[n - i * i - j * j] != null) {
					System.out.println(i + " " + j + " " +strings[n - i * i - j * j]);
					flag =  true;
					break;
				}
			}
			if (flag) break;
		}
	}
}
1227、分巧克力(简单)(二分搜索答案)

在这里插入图片描述
一块巧克力能够分出来的块数 = (长 / 分的边长) * (宽 / 分的边长),那不就跟上一题一样,直接二分找答案~

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	static int[] h;
	static int[] w;
	static int n, k;
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		k = scan.nextInt();
		h = new int[n];
		w = new int[n];
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			h[i] = scan.nextInt();
			max = Math.max(h[i], max);
			w[i] = scan.nextInt();
			max = Math.max(w[i], max);
		}
		// 二分答案,找最后一个满足要求的边,就是最大边长
		int left = 1;
		int right = max;
		int mid;
		int ans = -1;
		while (left <= right) {
			mid = left + (right - left) / 2;
			if (check(mid)) {
				ans = mid;
				// 尝试扩大边长
				left = mid + 1;
			} else {
				right = mid - 1;
			}
		}
		System.out.println(ans);
	}
	public static boolean check(int len) {
		int count = 0;
		for (int i = 0; i < n; i++) {
			count += (h[i] / len) * (w[i] / len);
		}
		return count >= k;
	}
}
99、激光炸弹(简单)(二维前缀和)

在这里插入图片描述
本质是求二维前缀和,然后遍变成为R的所有正方形的最大和,如果发现最后ans还是最小值,说明导弹太大了,可以全部炸完。

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int r = scan.nextInt();
		int[][] map = new int[5010][5010];
		for (int i = 0; i < n; i++) {
			int a = scan.nextInt();
			int b = scan.nextInt();
			int w = scan.nextInt();
			// 为了方便计算前缀和
			// 注意不同目标可以处于同一位置
			map[a + 1][b + 1] += w;
		}
		// 计算前缀和
		int[][] preSum = new int[5010][5010];
		for (int i = 1; i <= 5001; i++) {
			for (int j = 1; j <= 5001; j++) {
				preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + map[i][j];
			}
		}
		// 炸毁范围是正方形
		// 遍历右下角坐标
		int ans = Integer.MIN_VALUE;
		for (int i = r; i <= 5001; i++) {
			for (int j = r; j <= 5001; j++) {
				int tmp = preSum[i][j] - preSum[i - r][j] - preSum[i][j - r] + preSum[i - r][j - r];
				ans = Math.max(ans, tmp);
			}
		}
		// 如果ans还是最小值,说明导弹太大了!
		if (ans == Integer.MIN_VALUE) System.out.println(preSum[5000][5000]);
		else System.out.println(ans);
	}
}
※1230、k倍区间(中等)(前缀和+数学转换)

在这里插入图片描述
一看就是前缀和,但是会超时,尝试优化。

之前刷过的题…又搞忘了…一刷根本不够啊!

考虑前缀和区间[i, j] = preSum[j + 1] - preSum[i](这里i, j从0开始算)是k的倍数,那么preSum[j + 1] % k = preSum[i] % k,所以我们只用去找相同的前缀和%k有多少个即可。我们用cnt来记录前缀和%k的个数,例如:前缀和%k = 1,那么cnt[1]++。

注意,这里我们的 i 是从0下标开始算的,而preSum[0]是没有意义的,或者说是=0的,所以cnt[0]预先赋值 = 1,代表已经有一个=0的情况了。

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int k = scan.nextInt();
		int[] num = new int[n];
		// 前缀和
		long[] preSum = new long[n + 1];
		for (int i = 0; i < n; i++) {
			num[i] = scan.nextInt();
			preSum[i + 1] = preSum[i] + num[i];
		}
		long[] cnt = new long[n + 1];
		// 这里一定要初始化为1,或者修改下面for循环i下标=0也可以
		cnt[0] = 1;
		long ans = 0;
		for (int i = 1; i < n + 1; i++) {
			ans += cnt[(int)(preSum[i] % k)];
			cnt[(int)(preSum[i] % k)]++;
		}
		System.out.println(ans);
	}
}

后面内容请看第三、四讲博客哦~

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@u@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值