反正听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);
}
}
后面内容请看第三、四讲博客哦~