Java前置知识
输入流:
(在 Java面向对象编程-CSDN博客里面有提过相关知识------IO流)
java
// 快读快写
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
// 常见读入
static Scanner sc = new Scanner(System.in);
Scanner:
基本类型和字符串的读取方法
- next():查找并返回下一个完整标记(token),即下一个由分隔符界定的字符串。
- nextLine():返回当前行的剩余部分,并移动到下一行。注意,nextLine() 在使用其他 nextXxx() 方法后可能需要单独处理换行符的问题。
- nextInt():扫描下一个整数作为 int 类型。
- nextLong():扫描下一个整数作为 long 类型。
- nextDouble():扫描下一个浮点数作为 double 类型。
- nextFloat():扫描下一个浮点数作为 float 类型。
- nextByte():扫描下一个字节值作为 byte 类型。
- nextShort():扫描下一个短整数值作为 short 类型。
- nextBoolean():扫描下一个布尔值作为 boolean 类型。
- nextBigInteger() 和 nextBigDecimal():分别扫描下一个大整数或大浮点数。
检查下一个元素是否存在的方法
- hasNext():如果输入有另一个标记,则返回 true。
- hasNextLine():如果有另一行输入,则返回 true。
- hasNextInt()、hasNextLong()、hasNextDouble() 等等:检查下一个标记是否为指定类型的值。
设置和获取分隔符
- useDelimiter(String pattern) 或 useDelimiter(Pattern pattern):设置此扫描器用于划分标记的分隔符模式。
- delimiter():返回此扫描器当前的分隔符模式。
其他有用的方法
- close():关闭此扫描器。如果这个扫描器已经关闭,则调用此方法将不起作用。
- reset():重置此扫描器,使其丢弃所有局部状态并恢复到其初始状态。
- findWithinHorizon(Pattern pattern, int horizon):尝试找到与给定模式匹配的输入子序列。
- skip(Pattern pattern):跳过输入中的所有输入,直到遇到与给定模式匹配的内容为止。
注意事项
当使用 nextXXX() 方法(如 nextInt()、nextDouble() 等)读取基本类型数据后,如果接下来想用 nextLine() 读取一整行输入,应该先调用一次 nextLine() 来消耗掉之前的换行符。否则,nextLine() 可能会立即返回一个空字符串。
使用完 Scanner 后应当调用 close() 方法以释放资源,尤其是在读取文件时。
BufferedWriter
- 缓冲写入:BufferedWriter 可以显著减少磁盘I/O次数,从而提高写入速度。
- 自动换行:newLine() 方法可以根据当前平台添加正确的换行符。
- 批量刷新:使用 flush() 方法确保所有缓冲的数据都被写入目标输出流。
BufferedReader
- 按行读取:BufferedReader 的 readLine() 方法可以高效地读取整行文本。
- 字符数组读取:如果需要逐字符处理,可以使用 read(char[] cbuf, int off, int len) 方法批量读取字符到一个字符数组中。
注意事项:
在读取多个值时,通常会先读取一行,然后用空格分割字符串,再转换为相应的类型。
使用 trim() 方法去除多余的空白字符,特别是换行符,这在读取整数或浮点数时非常重要。
二维字符数组读取:
在Java中读取二维字符数组,通常是通过读取每一行的字符串,然后将这些字符串转换为字符数组,并将其分配给二维字符数组的相应行。
java
for (int i = 0; i < 4; i ++) {
String t = sc.nextLine();
g[i] = t.toCharArray();
}
递归和递推
指数型枚举
**题目简述:**从1~n中选取任意个数字,升序排列
解题思路:
对于每一个数字都有两种选择,选或者不选,从根节点开始搜索(空节点,什么都不选),可以类似于看作为有n位数字位,你往下搜索的过程中,你都可以选择向这个空位是否填数字,当你做完最后一个选择时(对第n个数进行选择之后),就可以递归回溯了,整个搜索的过程可以画出一个递归搜索树,如下图
java
import java.util.Scanner;
public class Main{
private static final int N = 20;
private static int n;
private static boolean[] st = new boolean[N];
public static void dfs(int u) {
if (u == n + 1)
{
for (int i = 1; i <= n; i ++)
if (st[i])
System.out.printf("%d ", i);
System.out.println();
return; // 回溯
}
// 选当前数字
st[u] = true;
dfs(u + 1);
// 不选当前数字
st[u] = false;
dfs(u + 1);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
dfs(1);
sc.close();
}
}
排列型枚举
**题目简述:**将1~n进行排列组合
**题目思路:**考虑每一位可以填哪几个数,用过的数字需要标记
补充:
输入输出流
InputStream/OutputStream 类:
InputStream.read(): 读取一个字节。
OutputStream.write(byte b): 写入一个字节。
BufferedReader/BufferedWriter 类:
BufferedReader.readLine(): 读取一行。
BufferedWriter.write(String str): 写入字符串。
java
import java.util.Scanner;
import java.io.*;
public class Main {
private static final int N = 15;
private static int n;
private static boolean[] st = new boolean[N];
private static int[] path = new int[N];
private static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
private static void dfs(int u) throws IOException{
// 递归出口
if (u == n) {
for (int i = 0; i < n; i ++)
out.write(path[i] + " ");
out.write("\n");
return; // 回溯
}
// 向第u位填数
for (int i = 1; i <= n; i ++) {
if (!st[i]) {
path[u] = i; // 第u位填上i
st[i] = true; // 代表在当前分支上 i已经被用过了
dfs(u + 1); // 填下一位
st[i] = false; // 恢复现场,回溯过程
}
}
}
public static void main(String[] args) throws IOException{
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
dfs(0);
out.flush();
out.close();
}
}
简单递推
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
if (n == 0) System.out.println(0);
else if (n == 1) System.out.println("0");
else if (n == 2) System.out.println("0 1");
else {
int a = 0, b = 1, c = 0;
System.out.printf("0 1");
for (int i = 3; i <= n; i ++) {
c = a + b;
System.out.printf(" %d", c);
a = b;
b = c;
}
}
}
}
费解的开关
解析:
每一盏灯都有两种选择 : 按或者不按。
通过递推发现规律:
- 当第一行的操作方案确定时,剩余行的操作方案也确定了
2.前一行的状态影响下一行的操作
举例:
假设第一行选择某一种方案进行操作后的状态如图所示:
第一行的状态决定了第二行的操作(要使得第一行灯灭的格子变亮,就必须对该格子下方的方格进行操作):
操作完成后第一行的格子全亮
以此类推......
思路:
1.枚举第一行格子的每一种操作方案:使用二进制表示操作方式,1表示按,0表示不按
2.枚举前四行的状态,操作下一行,使得前四行的格子全亮
3.判断最后一行格子是否全亮
题目:
你玩过"拉灯"游戏吗?
25盏灯排成一个 5×5 的方形。
每一个灯都有一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。
给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。
输入格式
第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。
以下若干行数据分为 n组,每组数据有 5 行,每行 5 个字符。
每组数据描述了一个游戏的初始状态。
各组数据间用一个空行分隔。
输出格式
一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。
对于某一个游戏初始状态,若 6 步以内无法使所有灯变亮,则输出 −1。
数据范围
0<n≤500
输入样例:
3 00111 01011 10001 11010 11100 11101 11101 11110 11111 11111 01111 11111 11111 11111 11111
输出样例:
3 2 -1
java
import java.io.*;
import java.util.Scanner;
public class Main {
static int n;
static final int N = 510;
static char[][] c = new char[N][N];
static int[][] g = new int[N][N];
static int[][] backup = new int[N][N];
static int[] dx = {0, 1, 0, -1, 0};
static int[] dy = {0, 0, 1, 0, -1};
// 改变上下左右四个灯的状态
private static void turn(int x, int y)
{
for (int i = 0; i < 5; i ++)
{
int a = x + dx[i], b = y + dy[i];
if (a >= 1 && a <= 5 && b >= 1 && b <= 5)
{
if (g[a][b] == 0) g[a][b] = 1;
else g[a][b] = 0;
}
}
}
// 当前状态的最小步数
private static int bfs(int[][] g) {
int cnt = 0;
for (int i = 1; i <= 4; i ++)
{
for (int j = 1; j <= 5; j ++)
{
if (g[i][j] == 0)
{
turn(i + 1, j);
cnt ++;
}
}
}
for (int i = 1; i <= 5; i ++)
if (g[5][i] == 0)
return -1;
return cnt;
}
// 将g数组重置
private static void AmemcpyB()
{
for (int i = 1; i <= 5; i ++)
{
for (int j = 1; j <= 5; j ++)
{
g[i][j] = backup[i][j];
}
}
}
public static void main(String[] args) throws IOException{
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
while (true)
{
if (n == 0) break;
if (n >= 1)
sc.nextLine();
for (int i = 1; i <= 5; i ++)
{
String str;
str = sc.nextLine();
c[i] = str.toCharArray();
}
for (int i = 1; i <= 5; i ++)
{
for (int j = 1; j <= 5; j ++)
{
g[i][j] = c[i][j - 1] - '0';
backup[i][j] = g[i][j];
}
}
int ans = 10; // 最终步数
for (int op = 0; op < (1 << 5); op ++)
{
AmemcpyB();
int res = 0;
for (int i = 0; i < 5; i ++)
{
if ((op >> i & 1) == 1)
{
turn(1, i + 1);
res ++;
}
}
int x = bfs(g);
if (x != -1)
{
res += x;
ans = Integer.min(ans, res);
}
}
if (ans > 6) ans = -1;
System.out.println(ans);
n --;
}
}
}
组合型枚举
AcWing 93. 递归实现组合型枚举
输入样例:
5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
java
import java.util.Scanner;
public class Main {
static final int N = 50;
static int n, m;
static boolean[] st = new boolean[N];
static int[] path = new int[N];
private static void dfs(int u, int last) {
if (m - u > n - last) return;
if (u == m + 1) {
for (int i = 1; i <= m; i ++)
System.out.printf("%d ", path[i]);
System.out.println();
return;
}
for (int i = last; i <= n; i ++) {
if (!st[i]) {
path[u] = i;
st[i] = true;
dfs(u + 1, i);
st[i] = false;
}
}
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
dfs(1, 1);
scanner.close();
}
}
带分数
解析: n = a + b / c
经过变形得:b = nc - ac
java
import java.util.Scanner;
public class Main {
static final int N = 15;
static int n;
static boolean[] st = new boolean[N]; // 标记用过的数字
static boolean[] backup = new boolean[N];
static int res = 0;
// n = a + b / c
// b = nc - ac
// 枚举a的全排列: u表示当前用掉的位数,a表示当前啊的值
private static boolean check(int a, int c) {
long b = (long)n * c - (long)a * c;
if (a == 0 || b == 0 || c == 0) return false;
System.arraycopy(st, 0, backup, 0, N);
while (b > 0) {
int x = (int)(b % 10);
b /= 10;
if (x == 0 || backup[x]) return false;
backup[x] = true;
}
for (int i = 1; i <= 9; i ++) {
if (!backup[i]) return false;
}
return true;
}
private static void dfs_c(int u, int a, int c) {
if (u == 9) return; // 用完了
if (check(a, c)) res ++;
for (int i = 1; i <= 9; i ++) {
if (!st[i]) {
st[i] = true;
dfs_c(u + 1, a, c * 10 + i);
st[i] = false;
}
}
}
private static void dfs_a(int u, int a) {
if (u == 9) return; // 全部用光了
if (a != 0) dfs_c(u, a, 0);
// 枚举可填的数
for(int i = 1; i <= 9; i ++) {
if (!st[i]) {
st[i] = true; // 表示当前这个数字已用
dfs_a(u + 1, a * 10 + i);
st[i] = false; // 恢复现场
}
}
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
n = scanner.nextInt();
dfs_a(0, 0);
System.out.println(res);
}
}
飞行员兄弟
输入样例:
-+--
-+--
输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4
思路:
这一题的数据范围较小,暴力枚举每一种方案,运用二进制表示操作方案
java
import java.util.Scanner;
public class Main {
private static final int N = 4;
private static char[][] g = new char[N][N];
private static char[][] backup = new char[N][N];
public static void Print_Char(char[][] str) {
for (int i = 0; i < 4; i ++) {
for (int j = 0; j < 4; j++) {
System.out.printf("%c", str[i][j]);
}
System.out.println();
}
System.out.println();
}
public static void op_c(char[][] str, int x, int y) {
if (str[x][y] == '+') str[x][y] = '-';
else if (str[x][y] == '-') str[x][y] = '+';
}
// 改变行列
public static void Change_col_row(char[][] str, int x, int y) {
// 改变x这一行,y这一列
for (int i = 0; i < 4; i ++) {
op_c(str, x, i);
op_c(str, i, y);
}
op_c(str, x, y);
}
// 判断冰箱是否打开
public static boolean is_success(char[][] str) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (str[i][j] == '+') {
return false;
}
}
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
for (int i = 0; i < 4; i ++) {
String t = sc.nextLine();
g[i] = t.toCharArray();
}
for (int i = 0; i < (1 << 16); i ++) {
for (int k = 0; k < 4; k ++) {
System.arraycopy(g[k], 0, backup[k], 0, N);
}
int cnt = 0;
for (int k = 0; k < 16; k ++) {
if (((i >> k)&1) == 1) {
Change_col_row(backup, k/4, k%4);
cnt ++;
}
}
boolean flag = is_success(backup);
if (flag) {
System.out.println(cnt);
for (int k = 0; k < 16; k ++) {
if (((i>>k)&1) == 1) {
System.out.printf("%d %d\n", k / 4 + 1, k % 4 + 1);
}
}
}
}
}
}
翻硬币
输入样例1:
**********
o****o****
输出样例1:
5
输入样例2:
*o**o***o***
*o***o**o***
输出样例2:
1
java
import java.util.Scanner;
public class Main {
private static final int N = 110;
private static char[] a = new char[N];
private static char[] b = new char[N];
public static void turn(char[] str, int x) {
if (str[x] == '*') str[x] = 'o';
else if (str[x] == 'o') str[x] = '*';
if (str[x + 1] == '*') str[x + 1] = 'o';
else if (str[x + 1] == 'o') str[x + 1] = '*';
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
a = scanner.next().toCharArray();
b = scanner.next().toCharArray();
int cnt = 0;
for (int i = 0; i < a.length - 1; i ++) {
if (a[i] != b[i]) {
cnt ++;
turn(a, i);
}
}
System.out.println(cnt);
}
}
二分与前缀和
基础知识:
机器人跳跃问题
java
输入样例1:
5
3 4 3 2 4
输出样例1:
4
输入样例2:
3
4 4 4
输出样例2:
4
输入样例3:
3
1 6 4
输出样例3:
3
思路:
从左边开始查找的二分问题
java
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private static final int N = 1000010;
private static int[] h = new int[N];
private static int n;
// 判断初始能量为e是否成功
public static boolean check(int e) {
for (int i = 0; i < n; i ++) {
e += (e-h[i]);
if (e < 0) return false;
if (e >= N) return true;
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int l = 0, r = N;
for (int i = 0; i < n; i ++) {
h[i] = sc.nextInt();
}
// 二分法
while (l < r) {
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
System.out.println(l);
}
}
四平方和
java
输入样例:
5
输出样例:
0 0 1 2
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for (int a = 0; a * a <= n; a ++) {
for (int b = a; a * a + b * b <= n; b ++) {
for (int c = b; a * a + b * b + c * c <= n; c ++) {
int x = n - a * a - b * b - c * c;
if (x < c * c) continue;
else {
double t = Math.sqrt((double)x);
if (t - (int)t == 0) {
System.out.printf("%d %d %d %d\n", a, b, c, (int)t);
return;
}
}
}
}
}
}
}
分巧克力
java
输入样例:
2 10
6 5
5 6
输出样例:
2
思路:
分出来的巧克力边长越大越好,所以可以采用从右边开始查找的二分法
java
import java.util.Scanner;
public class Main {
private static final int N = 1000010;
private static int[] h = new int[N];
private static int[] w = new int[N];
public static boolean check(int x, int n, int k) {
int sum = 0;
for (int i = 1; i <= n; i ++) {
sum += (h[i] / x) * (w[i] / x);
}
if (sum >= k) return true;
return false;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
for (int i = 1; i <= n; i ++) {
h[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
int l = 1, r = h[1];
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(mid, n, k)) l = mid;
else r = mid - 1;
}
System.out.println(r);
}
}
激光炸弹
java
输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
思路:
二维前缀和
java
import java.util.Scanner;
public class Main {
private static final int N = 5100;
private static int[][] g = new int[N][N];
private static int[][] s = new int[N][N];
private static int n, r;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
r = scanner.nextInt();
for (int i = 1; i <= n; i ++) {
int x, y, c;
x = scanner.nextInt();
y = scanner.nextInt();
c = scanner.nextInt();
g[x + 1][y + 1] += c;
}
// 求二维前缀和
for (int i = 1; i < N; i ++) {
for (int j = 1; j < N; j ++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + g[i][j];
}
}
if (r >= N) {
System.out.println(s[N - 1][N - 1]);
return;
}
// 枚举起点
int res = Integer.MIN_VALUE;
for (int i = 1; i < N - r; i ++) {
for (int j = 1; j < N - r; j ++) {
// (i, j) 到 (i + r - 1, j + r - 1)的和
int sum = s[i + r - 1][j + r - 1] - s[i - 1][j + r - 1] - s[i + r - 1][j - 1] + s[i - 1][j - 1];
res = Math.max(res, sum);
}
}
System.out.println(res);
}
}
K倍区间
java
输入样例:
5 2
1
2
3
4
5
输出样例:
6
思路:
注意:
cnt[(int)(s[i] % k + k) % k]
因为s[i]的数据类型为long,数组下标可能会越界变成负数,所以需要将其操作使其为非负数
java
import java.util.Scanner;
public class Main {
private static final int N = 100010;
private static int[] a = new int[N];
private static long[] s = new long[N];
private static int[] cnt = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
long sum = 0;
for (int i = 1; i <= n; i ++) {
a[i] = scanner.nextInt();
s[i] = s[i - 1] + a[i];
cnt[(int)(s[i] % k + k) % k] ++;
}
for (int i = 0; i < k; i ++) {
if (cnt[i] != 0)
sum += (long)(cnt[i] - 1) * cnt[i] / 2;
}
System.out.println(sum + cnt[0]);
}
}
数学与简单Dp
小编:
人甚至不能共情自己。。。花了五个小时证明买不到的数目这题。。。
买不到的数目(裴蜀定理)
思路:
1.只有互质的情况下才有解
2.当两数互质时,根据裴蜀定理 ax+by=1
3.具体方法:
(1).通过暴力枚举,观察结果,发现规律,提出假设。
n = (p - 1)*(q - 1) - 1 = pq - p - q
java
import java.util.Scanner;
public class Main {
private static int p, q;
public static boolean dfs(int m, int p, int q) {
if (m == 0) return true;
if (m >= p && dfs(m - p, p, q)) return true;
if (m >= q && dfs(m - q, p, q)) return true;
return false;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(); // 输入个数
while (n -- > 0) {
p = scanner.nextInt();
q = scanner.nextInt();
int res = 0;
for (int i = 1; i <= 1000; i ++) {
if (!dfs(i, p, q)) res = i;
}
System.out.println(res);
}
}
}
(2).证明假设:
代码:
java
import java.util.Scanner;
public class Main {
private static int p, q;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
p = scanner.nextInt();
q = scanner.nextInt();
System.out.println(p * q - p - q);
}
}
注意:如有错误,欢迎指正!!!