Wust Java Club 2022-2023上半学年中期考核
Wust Java Club 2022-2023上半学年中期考核
前言
提交时的注意事项
- 不可写入包名,如
package edu.wust
- 必须有且只能有一个公有类
public class Main
,若有其他类,不应给其赋为公有,如class test
- 一定要导入使用的包名,如
import java.util.Scanner;
OnlineJudge的各种返回
CE:编译错误(黄黄的),一般是类名错误,即不是public class Main
,或者未导入所使用的包,如import java.util.Scanner;
RE:运行时错误(紫紫的),一般是数组越界,类型转换错误,栈溢出,写入包名package edu.wust
WA:答案错误(红红的),样例过了不代表这题能过
TLE:运行超时,检查算法时间复杂度,若超出时间限制不多,可进行些许常数优化(如位运算,输入输出优化)
MLE:超出空间限制,一般是数组开的过大,dp问题可以考虑进行维度优化
输入
Scanner
在读入量小时效果还行
但在读入量十分大时,显得性能十分低下
这里建议使用BufferedReader
或者StreamTokenizer
StreamTokenizer
:优点是占用内存少,但是它只存储double
及String
类型,若要读入int
或者long
类型,只能进行强制转换,但是double
类型存储多位整数的时候会转化为科学计数法,导致精度降低
更多细节请自行搜索
Barmecide Feast
知识点考察:
- 数学推导
题目链接:T291123 Barmecide Feast - 洛谷
原题链接:无
想法来源:《具体数学》
思路
题意:直线最多能将平面分割分割成多少部分?
设\(n\)条直线最多能分割出的平面个数为\(S_n\)
公式推导:
递推公式:\(S_n=S_{n-1}+n\)
由累加法得:\(S_n-S_0=1+2+...+n\)
由\(S_0=1\)及等差数列求和公式得:\(S_n=\dfrac{(1+n)\times n}{2}+1\)
时间限制卡的比较紧,使用BufferedReader
读入
关注数据范围两数相乘超出int
范围,使用long
类型
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));
long n = Long.parseLong(in.readLine());
System.out.println((n * (n + 1) >> 1) + 1);
}
}
Tower of Hanoi
知识点考察:
- 数学推导
- 快速幂
- 递推
题目链接:T291125 Tower of Hanoi - 洛谷
原题链接:无
想法来源:《具体数学》
设圆盘总数为\(n\),从初始状态到目标状态的最少步数为\(S_n\)
公式推导:
递推公式:\(S_n=2\times S_{n-1}+1\)
等比数列构造:\(S_n+1=2\times (S_{n-1}+1)\)
即:\(\dfrac{S_n+1}{S_{n-1}+1}=2\)
由累乘法得:\(\dfrac{S_n+1}{S_0+1}=2^n\)
由\(S_0=0\)得:\(S_n=2^n+1\)
观察数据范围\(n\in[0,10^{18}]\),使用快速幂及long
类型存储,使用int
类型读入long
大小的数会显示RE
乘法运算过程中取模 与 对结果取模 最终结果相同
即\(a\times b\ \% MOD=(a\ \% MOD)\times(b\ \%MOD)\ \%MOD\)
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
static final long MOD = 998244353;
static long powMod(long a, long 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) {
final long n = sc.nextLong();
//减1后可能为负数,加上一个MOD防止结果为负
System.out.println((powMod(2, n) - 1 + MOD) % MOD);
}
}
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);
}
}
优化
原理:
-
一个位置的主对角线
↙↗
的横纵坐标之和相同 -
一个位置的次对角线
↖↘
的横纵坐标之差相同
设一个点的坐标为 \((x,y)\)
则其主对角线的坐标可表示为 \((x-t,y+t)\)
副对角线的坐标可表示为 \((x+t,y+t)\)
根据这个性质,可以使用一个一维数组来标记某一个对角线是否存在皇后
竖直向上的位置同样可以用一个一维数组来标记
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^{V}\times\sum_{i=1}^{2^x}p^{k_i}=p^{V}\times ans_x\)
所以,若每种方案的体积都增加\(V\),相当于乘以\(p^{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_{i=1}^x(1+p^{V_i})\)
而\(ans_0\)表示当前无任何物品,即\(V=0\)
\(\therefore ans_0=p^V=p^0=1\)
\(\therefore ans_x=\prod_{i=1}^x(1+p^{V_i})\)
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\) 一定为 \(x,2x,3x,4x,...,kx\) 的因子,则 \(k=\left\lfloor\dfrac{n}{x}\right\rfloor\)
所以 \(answer=k=\left\lfloor\dfrac{n}{x}\right\rfloor\)
解决该题
以 \(n=6\) 为例,即求 \(\sum_{i=1}^6f(i)\)
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 final 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]);
}
}
Permutation
知识点考察:
- \(DFS\)+回溯
原题链接:P1706 全排列问题 - 洛谷
不要使用System.out.printf("%5d",i);
和String.format("%5d",i)
固定位宽格式化输出,会\(MLE\)
DFS回溯法求全排列
对其访问过的点打上访问标记,在遍历时跳过已打过标记的点,以确保 每个点仅访问一次。
Code
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
//book[i]代表i已经被选择过了
static boolean[] book;
//存储当前的排列
static int[] num;
static int N;
static void dfs(int n) {
//已经搜索到最后一个数了
if (n > N) {
for (int i = 1; i <= N; ++i) {
System.out.print(" " + num[i]);
}
System.out.println();
}
//遍历所有数
for (int i = 1; i <= N; ++i) {
//如果i已经被选择,则跳过
if (book[i]) continue;
//给i这个数打上标记,代表选上i
book[i] = true;
//将i存入当前排列答案中
num[n] = i;
//继续搜索下一个数
dfs(n + 1);
//撤回标记
book[i] = false;
}
}
public static void main(String[] args) {
N = sc.nextInt();
num = new int[N + 1];
book = new boolean[N + 1];
dfs(1);
}
}
下一个排列
下一个排列的定义是:
给定数字序列的字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
对于非严格递减的序列没有下一个排列
简单来说就是,下一个数比当前数大且尽可能的小
推导
举个例子,求7562541
的下一个排列
对于一个序列
越靠前的数对整个序列的影响幅度越大,越靠后的数对整个序列的影响幅度越小
对于非严格递减的数没有下一个排列
所以要找到一个靠后且有下一个排列的最小单元序列,也就是最后一个相邻的升序元素对
我们发现(2,5)
是最后一个相邻的升序的元素对,因此,2541
是满足条件的最小单元序列(这个子序列从第二个元素开始,一定是非严格递减的)
我们需要让这个子序列2541
大一点点,于是在2541
的后缀541
中从后向前找第一个大于首元素的数字,也就是4
,让它与第一个元素2
进行交换
这样得到的就是以4
为首的最大排列4521
,对其后缀521
进行逆序操作,就能得到以4
为首的最小排列4125
因此得到了7562541
的下一个排列7564125
描述
下一个排列的描述:
- 从后向前查找第一个相邻的 升序 的元素对(代表这个后缀有下一个排列),即找到最后一个
arr[i]<arr[i+1]
。(此时[i+1,end)
是非严格递减的) - 在后缀
[i+1,end)
中找到最小的大于arr[i]
的数,记为arr[k]
。由于后缀[i+1,end)
是非严格递减的,所以从后向前找到第一个大于arr[i]
的数即可 - 交换
arr[i]
和arr[k]
。这时得到的后缀是以arr[k]
为首的最大的排列 - 对后缀
[i+1,end)
进行逆序操作,来得到以arr[k]
为首的最小的排列。 - 若在步骤1中无符合条件的相邻的元素对,则说明此时数组是非严格降序的,没有下一个排列了
Code
import java.util.Scanner;
public class Main {
public static void swap(int[] arr, int x, int y) {
arr[x] ^= arr[y] ^ (arr[y] = arr[x]);
}
//作用:生成下一个排列 (数组范围[l,r])
public static boolean nextPermutation(int[] arr, int l, int r) {
if (arr.length <= 1) return false;
int i = r - 1;
while (i >= l && !(arr[i] < arr[i + 1])) --i;
if (i == l - 1) return false;
int k = r;
while (arr[i] >= arr[k]) --k;
swap(arr, i, k);
for (int left = i + 1, right = r; left < right; ++left, --right) swap(arr, left, right);
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] arr = new int[n + 1];
for (int i = 1; i <= n; ++i) arr[i] = i;
do {
for (int i = 1; i <= n; ++i) System.out.print(" " + arr[i]);
System.out.println();
} while (nextPermutation(arr, 1, 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)));
}
}
0-1 Knapsack Problem
知识点考察:
- 01背包
题目链接:T291919 0-1 Knapsack Problem - 洛谷
原题链接:P1049 装箱问题 - 洛谷
01背包变式,体积就是价值
dp数组可定义为:
- 最小剩余空间
- 所用的最大空间,最后用\(V\)减去它
最小剩余空间
\(dp[i][j]\)代表 前\(i\)个物品有\(j\)个空间的背包 目前所剩的最少空间
以这种思路未写正确的,是未理解\(dp\)数组的含义
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int V = in.nextInt();
int N = in.nextInt();
int[] weight = new int[N + 1];
int[][] dp = new int[N + 1][V + 1];
for (int i = 1; i <= N; ++i) {
weight[i] = in.nextInt();
}
//初始化所有背包都剩V体积
for (int i = 0; i <= N; ++i) {
Arrays.fill(dp[i], V);
}
for (int i = 1; i <= N; ++i) {
for (int j = 0; j <= V; ++j) {
//如果背包放得下,且目前剩余空间也放得下
if (j >= weight[i] && dp[i - 1][j - weight[i]] >= weight[i]) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - weight[i]] - weight[i]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
System.out.println(dp[N][V]);
}
}
所用的最大空间
\(dp[i][j]\)代表前\(i\)个物品有\(j\)体积的背包,目前能使用的最大体积
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int V = in.nextInt();
int N = in.nextInt();
int[] weight = new int[N + 1];
int[][] dp = new int[N + 1][V + 1];
for (int i = 1; i <= N; ++i) {
weight[i] = in.nextInt();
}
for (int i = 1; i <= N; ++i) {
for (int j = 0; j <= V; ++j) {
if (j >= weight[i]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + weight[i]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
System.out.println(V - dp[N][V]);
}
}
空间优化
以下以对第二种方法进行优化作为示例
二维
观察\(dp\)数组,只与前一行有关,可使用滚动数组取模进行优化
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int V = in.nextInt();
int N = in.nextInt();
int[] weight = new int[N + 1];
int[][] dp = new int[N + 1][V + 1];
for (int i = 1; i <= N; ++i) {
weight[i] = in.nextInt();
}
for (int i = 1; i <= N; ++i) {
for (int j = 0; j <= V; ++j) {
if (j >= weight[i]) {
//取模运算,只使用两行数组
dp[i % 2][j] = Math.max(dp[(i - 1) % 2][j], dp[(i - 1) % 2][j - weight[i]] + weight[i]);
} else {
dp[i % 2][j] = dp[(i - 1) % 2][j];
}
}
}
//输出时同样需要取模
System.out.println(V - dp[N % 2][V]);
}
}
一维
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
int V = sc.nextInt(), n = sc.nextInt();
int[] v = new int[n + 1], dp = new int[V + 1];
for (int i = 1; i <= n; ++i) v[i] = sc.nextInt();
for (int i = 1; i <= n; ++i) {
for (int j = V; j >= v[i]; --j) {
dp[j] = Math.max(dp[j], dp[j - v[i]] + v[i]);
}
}
System.out.println(V - dp[V]);
}
}
Countdown to Death
知识点考察:
- 递推
- 数学推导
题目链接:T291920 Countdown to Death - 洛谷
原题链接:P8671 蓝桥杯 2018 国 AC 约瑟夫环 - 洛谷
想法来源:《具体数学》
思路
设总人数为\(n\),报数终点为\(k\),最后胜利的人的编号为\(S(n,k)\)
递推公式:\(S(n,k)=(S(n-1,k)+k) \% n\)
当最后只剩下一个人时,编号应为0
因为取模后,编号是从0开始的,所以结果需要加1
递归-栈溢出
使用递归的写法会因为栈溢出显示RE
import java.util.Scanner;
public class Main {
static int get(int n, int k) {
if (n == 1) return 0;
return (get(n - 1, k) + k) % n;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), k = sc.nextInt();
System.out.println(get(n, k) + 1);
}
}
递推
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), k = sc.nextInt();
int ans = 0;
for (int i = 1; i <= n; ++i) {
ans = (ans + k) % i;
}
System.out.println(ans + 1);
}
}