Wust Java Club 2022-2023上半学年中期考核
前言
提交时的注意事项
- 不可写入包名,如
package edu.wust
- 必须有且只能有一个公有类
public class Main
,若有其他类,不应给其赋为公有,如class test
- 一定要导入使用的包名,如
import java.util.Scanner;
OnlineJudge的各种返回
RE:运行时错误(紫紫的),一般是数组越界,类型转换错误,栈溢出
WA:答案错误(红红的),样例过了不代表这题能过
TLE:运行超时,检查算法时间复杂度,若超出时间限制不多,可进行些许常数优化(如位运算,输入输出优化)
MLE:超出空间限制,一般是数组开的过大,dp问题可以考虑进行维度优化
输入
Scanner
在读入量小时效果还行
但在读入量十分大时,显得性能十分低下
这里建议使用BufferedReader
或者StreamTokenizer
StreamTokenizer
:优点是占用内存少,但是它只存储double
及String
类型,若要读入int
或者long
类型,只能进行强制转换,但是double
类型存储多位整数的时候会转化为科学计数法,导致精度降低
更多细节请自行搜索
Maximum Path Sum
知识点考察:
- 动态规划
- 记忆化搜索
题目链接: T291913 Maximum Path Sum - 洛谷
原题链接:[P1216 USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷
想法来源: 第二周作业 018 最大和的路径Ⅰ
这题如果用Scanner
读入数据,很容易会\(MLE\),即超出空间限制
从上向下走
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(in.readLine());
int[][] a = new int[n + 1][n + 1];
for (int i = 1; i <= n; ++i) {
String[] line = in.readLine().split(" ");
for (int j = 1; j <= i; ++j) {
a[i][j] = Integer.parseInt(line[j - 1]);
}
}
//dp[i][j]代表 从上向下走 到达i行j列的最大路径和
int[][] dp = new int[n + 1][n + 1];
//单独处理第一个值
dp[1][1] = a[1][1];
for (int i = 2; i <= n; ++i) {
//单独处理i行第一个数和最后一个数,防止越界
//第一个数只能从 上方 走来
dp[i][1] = a[i][1] + dp[i - 1][1];
for (int j = 2; j < i; ++j) {
//第i行j列 只能 从i-1行的j-1列和j列 走来
dp[i][j] = a[i][j] + Math.max(dp[i - 1][j - 1], dp[i - 1][j]);
}
//最后一个数只能从 左上方 走来
dp[i][i] = a[i][i] + dp[i - 1][i - 1];
}
int ans = Integer.MIN_VALUE;
//我看到有的人是对最后一行dp数组排序
//而排序的时间复杂度是O( nlog n )
//不如一重循环 O(n)好
for (int i = 1; i <= n; ++i) ans = Math.max(ans, dp[n][i]);
System.out.println(ans);
}
}
从下向上走
从上向下走和从下向上走结果是一致的
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(in.readLine());
int[][] a = new int[n + 1][n + 1];
for (int i = 1; i <= n; ++i) {
String[] line = in.readLine().split(" ");
for (int j = 1; j <= i; ++j) {
a[i][j] = Integer.parseInt(line[j - 1]);
}
}
//dp[i][j]代表 从下向上走 到达i行j列的最大路径和
int[][] dp = new int[n + 1][n + 1];
//单独处理最后一行的值
for (int i = 1; i <= n; ++i) dp[n][i] = a[n][i];
for (int i = n - 1; i >= 1; --i) {
for (int j = 1; j <= i; ++j) {
//第i行j列的值由i+1行的j列和j+1列推得
dp[i][j] = a[i][j] + Math.max(dp[i + 1][j], dp[i + 1][j + 1]);
}
}
System.out.println(dp[1][1]);
}
}
Eight Queens Puzzle
知识点考察:
- \(DFS\)+回溯
题目链接:T291914 Eight Queens Puzzle - 洛谷
原题链接:[P1219 USACO1.5]八皇后 Checker Challenge - 洛谷
一个锻炼大家代码逻辑能力的题
Code
这份代码过不了最后一个测试点
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
//map[i][j]表示i行j列是否已经有皇后了
static boolean[][] map;
static int[] ans;
//记录有多少个解
static int count = 0, n;
//判断row行col列是否能放皇后
static boolean check(int row, int col) {
//判断(row,col)的 ↑ 上方是否有皇后
for (int i = 1; i <= row; ++i) {
if (map[i][col]) {
return false;
}
}
//判断(row,col)的 ↖ 对角线是否有皇后
for (int i = row - 1, j = col - 1; i >= 1 && j >= 1; --i, --j) {
//如果有皇后,则说明当前位置不能放皇后
if (map[i][j]) {
return false;
}
}
//判断(row,col) ↗ 的对角线是否有皇后
for (int i = row - 1, j = col + 1; i >= 1 && j <= n; --i, ++j) {
//如果有皇后,则说明当前位置不能放皇后
if (map[i][j]) {
return false;
}
}
//如果 ↖ ↑ ↗ 三个方向
return true;
}
//寻找第row行的皇后
static void dfs(int row) {
//如果已经超出棋盘,则说明找到了一个可行解
if (row > n) {
++count;
//如果是前三个解,则输出
if (count <= 3) {
for (int i = 1; i <= n; ++i) System.out.print(ans[i] + " ");
System.out.println();
}
return;
}
//判断row行i列能不能放皇后
for (int i = 1; i <= n; ++i) {
//如果(row,j)能放皇后,则继续向下搜索
if (check(row, i)) {
ans[row] = i;
//标记这个地方放了皇后
map[row][i] = true;
//搜索下一行
dfs(row + 1);
//回溯,撤回标记
map[row][i] = false;
}
}
}
public static void main(String[] args)throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(in.readLine());
map = new boolean[n + 1][n + 1];
ans = new int[n + 1];
dfs(1);
System.out.println(count);
}
}
优化
原理(自行思考):
-
一个位置的主对角线
↙↗
的横纵坐标之和相同 -
一个位置的次对角线
↖↘
的横纵坐标之差相同
根据这个性质,可以使用一个一维数组来标记某一个对角线是否存在皇后
竖直向上的位置同样可以用一个一维数组来标记
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
static boolean[] upperLeft;
static boolean[] upperRight;
static boolean[] top;
static int[] ans;
//记录有多少个解
static int count = 0, n;
//判断row行col列是否能放皇后
static boolean check(int row, int col) {
if (top[col]) return false;
if (upperLeft[row - col + n]) return false;
if (upperRight[row + col]) return false;
return true;
}
//寻找第row行的皇后
static void dfs(int row) {
//如果已经超出棋盘,则说明找到了一个可行解
if (row > n) {
++count;
//如果是前三个解,则输出
if (count <= 3) {
for (int i = 1; i <= n; ++i) System.out.print(ans[i] + " ");
System.out.println();
}
return;
}
//判断row行i列能不能放皇后
for (int i = 1; i <= n; ++i) {
//如果(row,j)能放皇后,则继续向下搜索
if (check(row, i)) {
ans[row] = i;
//标记这个地方放了皇后
top[i] = true;
upperLeft[row - i + n] = true;
upperRight[row + i] = true;
//搜索下一行
dfs(row + 1);
//回溯,撤回标记
top[i] = false;
upperLeft[row - i + n] = false;
upperRight[row + i] = false;
}
}
}
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(in.readLine());
top = new boolean[n + 1];
// 横纵坐标之差的范围在(-n,n)
// 由于数组无负数下标,给其加上一个偏移量保证它一定在整数范围内
upperLeft = new boolean[2 * n + 1];
//横纵坐标之和的范围在[2,2n]
upperRight = new boolean[2 * n + 1];
ans = new int[n + 1];
dfs(1);
System.out.println(count);
}
}
Whimsical 0-1 Knapsack Product Problem
知识点考察:
- 数学推导
题目链接:T291909 Whimsical 0-1 Knapsack Product Problem - 洛谷
原题链接:P7223 01 背包 - 洛谷
想法来源:找 01背包 的题目时,误打误撞找到一个骗人的题目,疯狂折磨出题人
递归枚举所有方案
所有的方案个数为 \(nums\le2^{1000000}\)
理想情况下,最多只能拿到测试点编号2~5的分数
注意要对MOD
使用finnal
关键字
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
static int n;
static int p;
static int[] w;
static long ans = 0;
static final int MOD = 998244353;//一定要用final关键字!!!
public static void main(String[] args) {
n = sc.nextInt();
p = sc.nextInt();
w = new int[n];
for (int i = 0; i < n; ++i) {
w[i] = sc.nextInt();
}
dfs(0,0);
System.out.println(ans);
}
static void dfs(int len, long sum) {
if (len == n) {
ans = (ans + pow(p, sum)) % MOD;
return;
}
dfs(len + 1, sum);//如果不选择当前物品
dfs(len + 1, sum + w[len]);//如果选择当前物品
}
static long pow(long x, long n) {//尽量使用位运算优化
long ans = 1;
for (; n != 0; n >>= 1, x = x * x % MOD) {
if ((n & 1) == 1) ans = ans * x % MOD;
}
return ans;
}
}
数学推导
前\(X\)个物品的总收益记为\(ans_x\),前\(X-1\)个物品的总收益记为\(ans_{x-1}\)
以下关注\(ans_x\)的性质
设每种方案的总体积为\(k_i\),其中 \(i\epsilon[1,2^x]\)
则\(ans_x=\sum_{i=1}^{2^x}p^{k_i}\)
对于所有方案都增加\(V\)体积
则总收益变为\(\sum_{i=1}^{2^x}p^{k_i+V}=p^{k_i+V}\times\sum_{i=1}^{2^x}p^{k_i}=p^{k_i+V}\times ans_x\)
所以,若每种方案的体积都增加\(V\),相当于乘以\(p^{k_i+V}\)
考虑第\(X\)个物品的 \(ans_x\)
每种物品仅有两种情况
- 不选该物品,由于任意一种方案的物品总体积不变,则对\(ans_{x-1}\)无影响
- 选择该物品,对任意一种方案的物品总体积增加\(V_x\),则\(ans_{x-1}\times p^{V_x}\)
\(ans_x\)为这两种情况的价值的和
则\(ans_x=ans_{x-1}+ans_{x-1}\times p^{V_x}=ans_{x-1}\times(1+p^{V_x})\)
\(\therefore \dfrac{ans_x}{ans_{x-1}}=1+p^{V_x}\)
由累乘法得:\(\dfrac{ans_x}{ans_0}=\prod_1^x(1+p^{V_x})\)
而\(ans_0\)表示当前无任何物品,即\(V=0\)
\(\therefore ans_0=p^V=p^0=1\)
\(\therefore ans_x=\prod_1^x(1+p^{V_x})\)
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
static final long MOD = 998244353;
static int n;
static long ans = 1l, p;
static int[] V;
static long powMod(long a, int b) {
long ret = 1;
for (; b != 0; b >>= 1, a = a * a % MOD)
if ((b & 1) == 1) ret = ret * a % MOD;
return ret;
}
public static void main(String[] args) {
n = sc.nextInt();
p = sc.nextLong();
V = new int[n];
for (int i = 0; i < n; ++i) V[i] = sc.nextInt();
for (int v : V) ans = ans * (1l + powMod(p, v)) % MOD;
System.out.println(ans);
}
}
Multiple
知识点考察:
- 简单数论
- 数学推导
原题链接:P1403 约数研究 - 洛谷
想法来源: 第一周题目 001 3或5的倍数
不可思议,纯暴力居然能拿\(80\)分,数据还得加强
思路
前置知识
-
\(a\) 能整除 \(b\) 用符号表示为 \(b\mid a\)
-
\(1\sim n\) 中约数(即因子)含 \(x\) 的个数 为 \(\left\lfloor\dfrac{n}{x}\right\rfloor\)
证明
对于 \(x\) 作为因子,\(x\) 一定为 \(2x,3x,4x,...,kx\) 的因子,则 \(k=\left\lfloor\dfrac{n}{x}\right\rfloor\)
所以 \(answer=k-1+1=k=\left\lfloor\dfrac{n}{x}\right\rfloor\)
解决该题
假设求\(f(6)\)
1 的约数:1
2 的约数:1,2
3 的约数:1, ,3
4 的约数:1,2, ,4
5 的约数:1, , , ,5
6 的约数:1,2,3, , ,6
正常思路:求每个数的约数个数,再求和
换种思路:求 \(1\sim n\) 中有多少个数约数含 \(i\) ,其中\(i\in [1,n]\)
证明:
\[由题得,要求的值为\sum_{i=1}^{n}f(i)=\sum_{i=1}^{n}\sum_{d\mid i}1 \]\[即\sum_{i=1}^{n}f(i)=\sum_{i=1}^{n}\sum_{d=1}^{n}{(d\mid i)} \]\[交换求和顺序\sum_{i=1}^{n}f(i)=\sum_{d=1}^{n}\sum_{i=1}^{n}{(d\mid i)} \]\[由前置知识2得 \sum_{i=1}^{n}f(i)=\sum_{d=1}^{n}\left\lfloor\dfrac{n}{d}\right\rfloor \]
Code
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int ans = 0;
for (int i = 1; i <= n; ++i) {
ans += n / i;
}
System.out.println(ans);
}
}
Climbing Stairs
知识点考察:
- 线性动态规划
- 递推
- 递归+记忆化数组
题目链接:T291917 Climbing Stairs - 洛谷
原题链接:P1192 台阶问题 - 洛谷
想法来源: 第四周题目 031 货币面值的组合问题
思路
设到达第\(x\)级台阶的方案数为\(f(x)\)
由于最多能迈\(K\)级台阶,所以\(f(x)=f(x-1)+f(x-2)+...+f(x-k)\)
递归写法
与斐波那契数列递归写法相同,但是数据量很大,重复计算的数据更多,所以需要记忆化数组进行剪枝优化
import java.util.Scanner;
public class Main {
static int[] memory;
static int MOD = 100003;
static int N, k;
static int getAns(int n) {
//如果这一级台阶的步骤数不为初值0,则说明已经计算过了
if (memory[n] != 0) return memory[n];
int ans = 0;
//最小台阶为0,取max防止负数台阶出现
for (int i = Math.max(0, n - k); i < n; ++i) {
ans = (ans + getAns(i)) % MOD;
}
//返回该台阶方案数之前,先赋值给记忆化数组
return memory[n] = ans;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
k = sc.nextInt();
memory = new int[N + 1];
//底部为0号台阶,不需要走,算一种方案
//1号台阶同理
memory[0] = memory[1] = 1;
System.out.println(getAns(N));
}
}
线性递推
与记忆化递归写法相同,只是求解顺序不同而已
import java.util.Scanner;
public class Main {
static int MOD = 100003;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), k = sc.nextInt();
//dp[i]代表到达第i级台阶的方案数
int[] dp = new int[n + 1];
//同理,0级与1级台阶方案数为1
dp[0] = dp[1] = 1;
for (int i = 2; i <= n; ++i) {
//dp[i]=dp[i-1]+dp[i-2]+...+dp[i-k]
for (int j = Math.max(0, i - k); j < i; ++j) {
dp[i] += dp[j];
dp[i] %= MOD;
}
}
System.out.println(dp[n]);
}
}
Exclusive or Lamp
知识点考察:
- 异或性质
题目链接:T291916 Exclusive or Lamp - 洛谷
原题链接:P1161 开灯 - 洛谷
想法来源:136. 只出现一次的数字 - 力扣
本来想考察异或的,结果用暴力的数组判断也能过 T_T
思路
因为只剩下一个编号是开着灯的,所以对所有编号异或,剩下的值就是开着的灯
Code
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
double t1;
int t2, ans = 0;
for (int i = 1; i <= n; ++i) {
t1 = sc.nextDouble();
t2 = sc.nextInt();
for (int j = 1; j <= t2; ++j) {
ans ^= (int) (t1 * j);
}
}
System.out.println(ans);
}
}
Euclidean‘s Jogging
知识点考察:
- 最大公约数\(gcd\)
- 最小公倍数\(lcm\)
题目链接:T291918 Euclidean‘s Jogging - 洛谷
原题链接:P4057 晨跑 - 洛谷
想法来源: 第一周题目 005 最小公倍数
思路
题目很长,但是最后仅要求三个数的最小公倍数
注意数据范围呀!!!
很多人\(85\)分就是因为没注意数据范围
int
的最大值大约为\(2\times 10^9\)
而题目最后三个测试点的数据范围为\(1\leq a,b,c\leq 100000\),即\(1\times 10^5\)
若三个数互质,则三个接近\(10^5\)的数相乘,会超过int
范围
需要使用long
类型
Code
import java.util.Scanner;
public class Main {
static long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
static long lcm(long a, long b) {
return a / gcd(a, b) * b;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long a = sc.nextLong(), b = sc.nextLong(), c = sc.nextLong();
System.out.println(lcm(a, lcm(b, c)));
}
}
标签:Java,Scanner,Club,int,static,2022,ans,sc,public
From: https://www.cnblogs.com/Cattle-Horse/p/16909986.html