蓝桥杯 2022年国赛真题
Java 大学B组
试题 A: 重合次数
试题 B: 数数
试题 C: 左移右移
试题 D: 窗口
试题 E: 迷宫
试题 F: 小球称重
试题 G: 背包与魔法
试题 H: 修路
试题 I: 围栏
试题 J: 好数之和
虽然名次上有些差强人意,但也算是在不佳的状态下一份不错的答卷了。
更新中…
试题 A: 重合次数
本题总分:5 分
【问题描述】
在同一天中,从上午 6 \small 6 6 点 13 \small 13 13 分 22 \small 22 22 秒到下午 14 \small 14 14 点 36 \small 36 36 分 20 \small 20 20 秒,钟表上的分针和秒针一共重合了多少次?
注意时针、分针、秒针都围绕中心做匀速运动。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个由大写字母组成的字符串,在提交答案时只填写这个字符串,填写多余的内容将无法得分。
494
import java.time.LocalTime;
public class Test {
public static void main(String[] args) { new Test().run(); }
LocalTime start = LocalTime.of(6, 13, 22);
LocalTime end = LocalTime.of(14, 36, 20);
void run() {
int cnt = 0;
for (; start.compareTo(end) <= 0; start = start.plusSeconds(1))
if (start.getMinute() == start.getSecond() && start.getSecond() != 0) ++cnt;
System.out.println(cnt);
}
}
按秒枚举时间,然后注意一下接近整点时,分针与秒针非常接近一直重合,而题面指出分针、秒针都围绕中心做匀速运动,故而这个地方需要特判一下。
试题 B: 数数
本题总分:5 分
【问题描述】
任何一个大于 1 \small 1 1 的正整数都能被分解为若干个质数相乘,比如 28 = 2 × 2 × 7 \small 28 = 2×2×7 28=2×2×7 被分解为了三个质数相乘。请问在区间 [ \small [ [ 2333333 \small 2333333 2333333 , \small , , 23333333 \small 23333333 23333333 ] \small ] ] 中有多少个正整数 可以被分解为 12 \small 12 12 个质数相乘?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
25606
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) { new Test().run(); }
int cnt = 0, start = 2333333, end = 23333333;
void run() {
int[] factor = new int[end + 1];
List<Integer> prime = new ArrayList();
for (int i = 2; i <= end; ++i) {
if (factor[i] == 0) {
factor[i] = 1;
prime.add(i);
}
if (i >= start &&
factor[i] == 12) ++cnt;
for (int p : prime) {
if (i * p > end) break;
factor[i * p] = factor[i] + 1;
if (i % p == 0) break;
}
}
System.out.print(cnt);
}
}
欧拉筛模板题,没啥好说的。
试题 C: 左移右移
时间限制:3.0s 内存限制:512.0MB 本题总分:10 分
【问题描述】
小蓝有一个长度为 N \small N N 的数组,初始时从左到右依次是 1 , 2 , 3 , ⋯ , N \small 1, 2 , 3, \cdots, N 1,2,3,⋯,N。
之后小蓝对这个数组进行了 M \small M M 次操作,每次操作可能是以下 2 \small2 2 种之一 : : :
1.
\small1.
1. 左移
x
\small x
x,即把
x
\small x
x 移动到最左边。
2.
\small2.
2. 右移
x
\small x
x,即把
x
\small x
x 移动到最右边。
请你回答经过 M \small M M 次操作之后,数组从左到右每个数是多少?
【输入格式】
第一行包含 2 2 2 个整数, N N N 和 M M M。
以下 M \small M M 行每行一个操作,其中 “ L x ” \small “L\ x” “L x” 表示左移 x \small x x, “ R x ” \small “R\ x” “R x” 表示右移 x \small x x。
【输出格式】
输出 N \small N N 个数,代表操作后的数组。
【样例输入】
5 3
L 3
L 2
R 1
【样例输出】
2 3 4 5 1
【样例说明】
样例中的数组变化如下
:
:
:
[
1
,
2
,
3
,
4
,
5
]
→
[
3
,
1
,
2
,
4
,
5
]
→
[
2
,
3
,
1
,
4
,
5
]
→
[
2
,
3
,
4
,
5
,
1
]
\small [1,2,3,4,5]→[3,1,2,4,5]→[2,3,1,4,5]→[2,3,4,5,1]
[1,2,3,4,5]→[3,1,2,4,5]→[2,3,1,4,5]→[2,3,4,5,1]
【评测用例规模与约定】
对于
50
%
\small 50\%
50% 的评测用例,
1
≤
N
,
M
≤
10000.
\small 1≤ N,M ≤10000.
1≤N,M≤10000.
对于
100
%
\small 100\%
100% 的评测用例,
1
≤
N
,
M
≤
200000
,
1
≤
x
≤
N
.
\small 1≤ N,M ≤200000,1≤ x≤ N.
1≤N,M≤200000,1≤x≤N.
懒惰删除法
import java.io.PrintWriter;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) { new Main().run(); }
void run() {
PrintWriter out = new PrintWriter(System.out);
Deque<Integer> L = new ArrayDeque();
Deque<Integer> R = new ArrayDeque();
int n = nextInt(), x;
char[] status = new char[n + 1];
for (int m = nextInt(); m-- > 0;)
if (nextChar() == 'L') {
status[x = nextInt()] = 'L';
L.push(x);
} else {
status[x = nextInt()] = 'R';
R.push(x);
}
for (; !L.isEmpty(); L.poll())
if (status[L.peek()] == 'L')
print(out, status, L.peek());
for (int i = 1; i <= n; ++i)
if (status[i] == '\0')
print(out, status, i);
for (; !R.isEmpty(); R.poll())
if (status[R.peek()] == 'R') {
status[R.peek()] = '$';
L.push(R.peek());
}
for (; !L.isEmpty(); L.poll())
print(out, status, L.peek());
out.flush();
}
void print(PrintWriter out, char[] status, int val) {
status[val] = '$';
out.print(val);
out.write(' ');
}
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
StreamTokenizer nextToken() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return in;
}
char nextChar() { return nextToken().sval.charAt(0); }
int nextInt() { return (int)nextToken().nval; }
}
若我们将 L L L 的操作数通过栈 S S S 存储起来,容易发现经过 M M M 次操作后的数组的前缀,一定是 S S S 自顶向上遍历的结果的一个子序列。
但每个操作数都有可以在某次操作后还被操作若干次,这也导致 S S S 中存储的操作数失去了 “正确性”,不过我们可以仿照 优先队列优化的 D i j k s t r a \small\rm Dijkstra Dijkstra 算法中的懒惰删除法,延迟删除某个操作数之前的操作记录,再效仿次法处理 M M M 次操作后的数组的后缀,
中间则是一段正整数序列的子序列,对于没有懒惰标记的操作数,在处理完结果前缀后依次输出即可。
重排大小
在一些涉及到若干序列的子序列的问题中,如最长公共上升序列,按某个序列的次序重排这个序列出现元素的“大小”是一种常见的处理手段。
容易观察到每次序列次序的变化都是单调易维护的,类似的,我们以每次操作复杂度在一个常数意义下,去维护这么一个“大小”关系序列,就能得到一个上界更为理想的算法。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.io.PrintWriter;
import java.io.IOException;
import static java.util.Arrays.sort;
public class Main {
public static void main(String[] args) { new Main().run(); }
void run() {
PrintWriter out = new PrintWriter(System.out);
int n = nextInt(), opt, low = 0, high = n;
long[] buffer = new long[n + 1];
for (int m = nextInt(); m-- > 0;) {
opt = nextChar();
buffer[nextInt()] =
opt == 'L' ? --low : ++high;
}
for (int i = 1; i <= n; ++i)
buffer[i] =
buffer[i] == 0 ? (i << 32 | i) : (buffer[i] << 32 | i);
sort(buffer, 1, n + 1);
for (int i = 1; i <= n; ++i) {
out.print((int)buffer[i]);
out.write(' ');
}
out.flush();
}
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
StreamTokenizer nextToken() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return in;
}
char nextChar() { return nextToken().sval.charAt(0); }
int nextInt() { return (int)nextToken().nval; }
}
试题 D: 窗口
时间限制:3.0s 内存限制:512.0MB 本题总分:10 分
【问题描述】
在平时使用电脑的过程中,经常会打开各种各样的窗口,各个窗口会在桌面上重叠,并按照一定的层次关系显示。有的窗口能够看到全部内容,而有的窗口只能看到局部。
现在给定一组操作桌面窗口的过程序列,请你通过 A S C I I \footnotesize\rm ASCII ASCII 艺术图来绘制最后桌面的状态。
已知桌面的大小为 N × M \small N × M N×M,即桌面高度为 N \small N N 个像素,宽度为 M \small M M 个像素,其中左上角坐标为 ( 0 , 0 ) \small (0,0) (0,0),右下角坐标为 ( N − 1 , M − 1 ) \small (N−1,M−1) (N−1,M−1)。
对于窗口的操作有如下 5 \small 5 5 种 : : :
1.
\small 1.
1.
n
e
w
\small\bf new
new 操作 - 打开一个新窗口
new
[PID]
[top]
[left]
[height]
[width]
\small\textit{\textbf{new [PID] [top] [left] [height] [width]}}
new [PID] [top] [left] [height] [width]
如
:
:
:
new
12
20
30
80
100
\small\textit{\textbf{new 12 20 30 80 100}}
new 12 20 30 80 100
表示打开一个
P
I
D
\small PID
PID 为
12
\small 12
12 的窗口,窗口左上角的坐标为
(
20
,
30
)
\small(20,30)
(20,30),该窗口宽度为
100
\small100
100 个像素,高度为
80
\small 80
80 个像素;新创建的窗口,其层级为顶层。
2.
\small 2.
2.
m
o
v
e
\small\bf move
move 操作 - 移动一个窗口
move
[PID]
[vertical]
[horizontal]
\small\textit{\textbf{move [PID] [vertical] [horizontal]}}
move [PID] [vertical] [horizontal]
如
:
:
:
move
12
-5
10
\small\textit{\textbf{move 12 -5 10}}
move 12 -5 10
表示将
P
I
D
\small PID
PID 为
12
\small 12
12 的窗口在垂直方向上移动
−
5
\small −5
−5 个像素,在水平方向上移动
10
\small 10
10 个像素。若窗口左上角原位置为
(
20
,
30
)
\small (20,30)
(20,30),此时则在
(
15
,
40
)
\small (15,40)
(15,40);移动后的窗口,其层级为顶层。
3.
\small 3.
3.
r
e
s
i
z
e
\small\bf resize
resize 操作 - 改变窗口大小
resize
[PID]
[height]
[width]
\small\textit{\textbf{resize [PID] [height] [width]}}
resize [PID] [height] [width]
如
:
:
:
resize
12
90
110
\small\textit{\textbf{resize 12 90 110}}
resize 12 90 110
表示保持左上角坐标不变的情况下,改变
P
I
D
\small PID
PID 为
12
12
12 的窗口大小,调整为高度
90
90
90 像素,宽度
110
110
110 像素;改变大小后的窗口,其层级为顶层。
4.
\small 4.
4.
c
l
o
s
e
\small\bf close
close 操作 - 关闭窗口
close
[PID]
\small\textit{\textbf{close [PID]}}
close [PID]
如
:
:
:
close
12
\small\textit{\textbf{close 12}}
close 12
表示关闭
P
I
D
\small PID
PID 为
12
\small 12
12 的窗口;
5.
\small 5.
5.
a
c
t
i
v
e
\small\bf active
active 操作 - 激活窗口
active
[PID]
\small\textit{\textbf{active [PID]}}
active [PID]
如
:
:
:
active
12
\small\textit{\textbf{active 12}}
active 12
表示激活
P
I
D
\small PID
PID 为
12
\small 12
12 的窗口,此时该窗口的层级被置为顶层。
【输入格式】
第 1 \small 1 1 行 : : : 2 \small 2 2 个正整数 N , M \small N,M N,M,表示桌面大小;
第 2 \small 2 2 行 : : : 1 \small1 1 个正整数 K \small K K,表示操作序列的长度;
第 3 ⋯ K + 2 \small 3\cdots K + 2 3⋯K+2 行 : : :每行一个操作,格式见题目描述。
【输出格式】
第 1 ⋯ N \small 1\cdots N 1⋯N 行 : : :每行 M \small M M 个字符,仅包含 ‘ . ’ , ‘ + ’ , ‘ − ’ , ‘ ∣ ’ , ‘ ’ \small ‘.’, ‘+’, ‘-’, ‘|’, ‘\ \ ’ ‘.’,‘+’,‘−’,‘∣’,‘ ’ 五种字符。
‘ . ’ \small ‘.’ ‘.’ 表示桌面背景,即该部分未被任何窗口覆盖, ‘ + ’ \small ‘+’ ‘+’ 表示窗口的四个角, ‘ − ’ \small ‘-’ ‘−’ 表示窗口的横边, ‘ ∣ ’ \small ‘|’ ‘∣’ 表示窗口的竖边, ‘ ’ \small ‘\ \ ’ ‘ ’ 表示窗口内部。
【样例输入】
7 10
8
new 1 0 3 2 5
new 2 4 4 2 5
new 3 3 3 4 6
resize 3 3 6
move 1 0 5
close 2
new 4 1 1 3 5
active 3
【样例输出】
........+-
.+---+..+-
.| |....
.+-+----+.
...| |.
...+----+.
..........
【评测用例规模与约定】
对于
100
%
\small 100\%
100% 的数据,
1
≤
N
,
M
≤
256
,
1
≤
K
≤
10000.
\small 1≤ N,M ≤256,1≤ K ≤10000.
1≤N,M≤256,1≤K≤10000.
输入数据保证
:
:
:
1.
\small1.
1. 同一时间不会有两个相同
P
I
D
\small PID
PID 的窗口存在,可能存在关闭某个
P
I
D
\small PID
PID 的窗口后,再新建一个同样
P
I
D
\small PID
PID 的窗口,
P
I
D
\small PID
PID 的取值范围为
1
≤
P
I
D
≤
100000
\small 1≤ PID≤100000
1≤PID≤100000;
2.
\small2.
2. 同时存在的窗口数量不超过
200
200
200 个;
3.
\small3.
3. 窗口尺寸不会小于
2
×
2
\small 2×2
2×2,即窗口高度和宽度均不会小于
2
\small 2
2,不会大于
N
×
M
\small N×M
N×M;
4.
\small4.
4. 窗口在移动过程中,可能有部分界面超出桌面显示范围;
5.
\small5.
5. 所有输入的数值均不超过
±
100000
\small ±100000
±100000。
6.
\small6.
6.
m
o
v
e
r
e
s
i
z
e
c
l
o
s
e
\small\bf move\ resize\ close
move resize close 只会对未关闭的窗口操作。
import java.io.PrintWriter;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.PriorityQueue;
import java.util.Queue;
public class Main {
public static void main(String[] args) { new Main().run(); }
char[][] desktop;
int n, m;
void run() {
desktop = new char[n = nextInt()][m = nextInt()];
PrintWriter out = new PrintWriter(System.out);
Queue<Window> queue = new PriorityQueue();
Window[] windows = new Window[100001];
for (int k = nextInt(); k-- > 0;) {
String opt = next();
int PID = nextInt();
if (opt.equals("close")) windows[PID] = null;
else {
switch (opt) {
case "new":
windows[PID] =
new Window(nextInt(), nextInt(), nextInt(), nextInt());
break;
case "move":
windows[PID].
move(nextInt(), nextInt());
break;
case "resize":
windows[PID].
resize(nextInt(), nextInt());
}
windows[PID].z = k;
}
}
for (int i = 1; i <= 100000; ++i)
if (windows[i] != null) queue.offer(windows[i]);
while (queue.size() > 0) {
Window now = queue.poll();
int x1 = now.top, x2 = now.top + now.height - 1,
y1 = now.left, y2 = now.left + now.width - 1;
for (int i = x1 + 1; i < x2; ++i)
for (int j = y1 + 1; j < y2; ++j) paint(i, j, ' ');
for (int j = y1; j <= y2; ++j) {
paint(x1, j, j > y1 && j < y2 ? '-' : '+');
paint(x2, j, j > y1 && j < y2 ? '-' : '+');
}
for (int i = x1 + 1; i < x2; ++i) {
paint(i, y1, '|');
paint(i, y2, '|');
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j)
out.write(desktop[i][j] == '\0' ? '.' : desktop[i][j]);
out.println();
}
out.flush();
}
void paint(int x, int y, char color) { if (x >= 0 && x < n && y >= 0 && y < m && desktop[x][y] == '\0') desktop[x][y] = color; }
class Window implements Comparable<Window> {
int z, top, left, height, width;
Window(int top, int left, int height, int width) {
this.resize(height, width);
this.move(top, left);
}
void move(int vertical, int horizontal) {
this.top += vertical;
this.left += horizontal;
}
void resize(int height, int width) {
this.height = height;
this.width = width;
}
@Override
public int compareTo(Window win) { return Integer.compare(this.z, win.z); }
}
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
StreamTokenizer nextToken() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return in;
}
int nextInt() { return (int)nextToken().nval; }
String next() { return nextToken().sval; }
}
阅读理解 + + + 大模拟,哪里来的天才出题人。
试题 E: 迷宫
时间限制:5.0s 内存限制:1.0GB 本题总分:15 分
【问题描述】
这天,小明在玩迷宫游戏。
迷宫为一个 n × n \small n×n n×n 的网格图,小明可以在格子中移动,左上角为 ( 1 , 1 ) \small (1,1) (1,1),右下角 ( n , n ) \small (n,n) (n,n) 为终点。迷宫中除了可以向上下左右四个方向移动一格以外,还有 m \small m m 个双向传送门可以使用,传送门可以连接两个任意格子。
假如小明处在格子 ( x 1 , y 1 ) \small (x_1,y_1) (x1,y1),同时有一个传送门连接了格子 ( x 1 , y 1 ) \small (x_1,y_1) (x1,y1) 和 ( x 2 , y 2 ) \small (x_2,y_2) (x2,y2),那么小明既可以花费 1 1 1 的步数向上下左右四个方向之一走一格 (不能越过边界),也可以花费 1 1 1 的步数通过传送门走到格子 ( x 2 , y 2 ) \small (x_2,y_2) (x2,y2) 去。
而对于同一个迷宫,小明每次进入的初始格子是在这 n × n \small n×n n×n 个格子中均匀随机的 (当然运气好可以直接随机到终点),他想知道从初始格子走到终点的最短步数的期望值是多少。
【输入格式】
输入共 1 + m \small 1 + m 1+m 行,第一行为两个正整数 n , m \small n,m n,m。
后面 m \small m m 行,每行四个正整数 x i 1 , y i 1 , x i 2 , y i 2 \small x_{i1},y_{i1},x_{i2},y_{i2} xi1,yi1,xi2,yi2 表示第 i \small i i 个传送门连接的两个格 子坐标。
【输出格式】
输出共一行,一个浮点数表示答案 (请保留两位小数)。
【样例输入】
2 1
1 1 2 2
【样例输出】
0.75
【样例说明】
由于传送门的存在,从 ( 1 , 1 ) \small (1,1) (1,1) 出发到终点 ( 2 , 2 ) \small (2,2) (2,2) 只需要一步;而从 ( 1 , 2 ) \small (1,2) (1,2) 和 ( 2 , 1 ) \small (2,1) (2,1) 出发也只需要向下 / \small/ /右走一步;从 ( 2 , 2 ) \small (2,2) (2,2) 出发需要 0 \small 0 0 步。所以步数期望为 1 + 1 + 1 + 0 2 × 2 = 0.75 \frac{1+1+1+0}{2×2}\small = 0.75 2×21+1+1+0=0.75。
【评测用例规模与约定】
对于
20
%
\small20\%
20% 的数据,保证
n
,
m
≤
20
\small n,m≤20
n,m≤20;
对于
100
%
\small100\%
100% 的数据,保证
n
,
m
≤
2000
;
x
i
1
,
y
i
1
,
x
i
2
,
y
i
2
≤
n
\small n,m≤2000; x_{i1},y_{i1},x_{i2},y_{i2}≤n
n,m≤2000;xi1,yi1,xi2,yi2≤n。
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) { new Main().run(); }
int[] linked = new int[1 << 22], next = new int[4001], ver = new int[1 << 22];
int[][] dist = new int[2001][2001], offset = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
int n, m, cur = 0;
long cnt = 1;
Deque<Integer> queue;
void run() {
for (n = nextInt(), m = nextInt(); m-- > 0;) {
int u = makePair(nextInt(), nextInt());
int v = makePair(nextInt(), nextInt());
++cur; next[cur] = linked[u]; linked[u] = cur; ver[cur] = v;
++cur; next[cur] = linked[v]; linked[v] = cur; ver[cur] = u;
}
queue = new ArrayDeque(n * 3 / 2);
queue.offer(makePair(n, n));
dist[n][n] = 1;
while (!queue.isEmpty()) {
int x = X(queue.peek());
int y = Y(queue.peek());
for (int i = 0; i < 4; ++i)
transfer(x + offset[i][0], y + offset[i][1], dist[x][y]);
for (int i = linked[queue.peek()]; i != 0; i = next[i])
transfer(X(ver[i]), Y(ver[i]), dist[x][y]);
queue.poll();
}
System.out.printf("%.2f", 1.0 * cnt / n / n - 1);
}
void transfer(int x, int y, int val) {
if (x > 0 && x <= n && y > 0 && y <= n && dist[x][y] == 0) {
cnt += dist[x][y] = val + 1;
queue.offer(makePair(x, y));
}
}
int makePair(int x, int y) { return (x << 11) | y; }
int Y(int pair) { return pair & 0x000007ff; }
int X(int pair) { return pair >>> 11; }
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
int nextInt() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return (int)in.nval;
}
}
广搜送分。
试题 F: 小球称重
时间限制:8.0s 内存限制:512.0MB 本题总分:15 分
【问题描述】
小蓝有 N \small N N 个小球,编号 1 \small 1 1 至 N \small N N。其中 N − 1 \small N−1 N−1 是正品,重量相同;有 1 \small 1 1 个是次品,重量比正品轻。
为了找出次品,小蓝已经用天平进行了 M \small M M 次称重,并且记录下来每次两边放的小球编号,和称重结果。
请你根据记录,判断还剩下几个小球有次品的嫌疑。
【输入格式】
第一行包含 2 \small 2 2 个整数 N 和 M。
以下包含 M \small M M 次称重记录,每个记录占 4 \small 4 4 行。
第一行是一个整数 K \small K K,表示天平两边各放了 K \small K K 个小球。
第二行包含 K \small K K 个整数,代表放在天平左边的小球编号。
第三行包含 K \small K K 个整数,代表放在天平右边的小球编号。
第四行是一个字符,为 ‘ > ’ , ‘ < ’ , ‘ = ’ \small ‘>’, ‘<’, ‘=’ ‘>’,‘<’,‘=’ 之一。 ‘ > ’ \small ‘>’ ‘>’ 代表左边比右边重, ‘ < ’ \small ‘<’ ‘<’ 代表左边比右边轻, ‘ = ’ \small ‘=’ ‘=’ 代表两边重量相等。
在一次称重中保证每个小球最多出现 1 \small 1 1 次。
【输出格式】
输出一个整数,代表答案。
【样例输入】
10 2
3
1 2 3
4 5 6
<
2
3 7
8 9
=
【样例输出】
2
【样例说明】
{
1
,
2
,
3
}
<
{
4
,
5
,
6
}
\small \{1, 2, 3\} < \{4, 5, 6\}
{1,2,3}<{4,5,6} 能判断出次品在
{
1
,
2
,
3
}
\small \{1, 2, 3\}
{1,2,3} 之中。
{
3
,
7
}
=
{
8
,
9
}
\small \{3, 7\} = \{8, 9\}
{3,7}={8,9} 能判断出
3
\small 3
3 不可能是次品。
所以只剩下
{
1
,
2
}
\small \{1, 2\}
{1,2} 可能是次品。
【评测用例规模与约定】
对于
40
%
\small 40\%
40% 的数据,
1
≤
N
≤
1
0
6
\small 1≤ N ≤10^6
1≤N≤106;
对于
100
%
\small 100\%
100% 的数据,
1
≤
N
≤
1
0
9
,
1
≤
M
≤
1
0
5
\small 1 ≤ N ≤ 10^9,1 ≤ M ≤ 10^5
1≤N≤109,1≤M≤105, 参与
M
\small M
M 次称重的小球总数
≤
1
0
6
.
\small ≤10^6.
≤106.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.*;
public class Main {
public static void main(String[] args) { new Main().run(); }
void run() {
int[] buffer = new int[2000000];
int n = nextInt(), count = 0, ans = 0;
Map<Integer, Integer> noir = new HashMap();
Set<Integer> blank = new TreeSet();
for (int m = nextInt(); m-- > 0;) {
int k = nextInt(), g = 2 * k;
for (int i = 0; i < g; ++i) buffer[i] = nextInt();
char opt = nextChar();
if (opt == '=')
for (int i = 0; i < g; ++i)
blank.add(buffer[i]);
else {
for (int i = opt == '>' ? 0 : k, j = i + k; i < j; ++i)
blank.add(buffer[i]);
for (int i = opt == '>' ? k : 0, j = i + k; i < j; ++i)
noir.put(buffer[i], noir.getOrDefault(buffer[i], 0) + 1);
++count;
}
}
for (Map.Entry<Integer, Integer> entry : noir.entrySet())
if (!blank.contains(entry.getKey()) && entry.getValue() == count) ++ans;
System.out.println(ans == 0 ? n - blank.size() : ans);
}
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer tok;
String next() {
while (tok == null || !tok.hasMoreTokens())
try {
tok = new StringTokenizer(in.readLine());
} catch (IOException e) {
e.printStackTrace();
}
return tok.nextToken();
}
int nextInt() { return Integer.parseInt(next()); }
char nextChar() { return next().charAt(0); }
}
因为仅存在一个比正品轻的次品小球,故而在两个数量等同的小球集合进行重量比较时,次品只可能出现在较轻的一方,也就是说 : : :
当两个数量等同的小球集合进行重量比较的结果为相等时,可以确定两个集合的小球均为正品;
当两个数量等同的小球集合进行重量比较的结果不相等时,可以确定重量较大的一个集合的小球均为正品。
换言之这又是一道模拟题,不过要特判一下所有比较结果均是相等的情况,此时所有没被鉴定为正品的小球均有可能是次品,输出 n n n 减去 被鉴定为正品的小球的数量 的值即可。
试题 G: 背包与魔法
时间限制:3.0s 内存限制:1.0GB 本题总分:20 分
【问题描述】
小蓝面前有 N \small N N 件物品,其中第 i \small i i 件重量是 W i \small W_i Wi,价值是 V i \small V_i Vi。她还有一个背包,最大承重是 M \small M M。
小蓝想知道在背包称重范围内,她最多能装总价值多少的物品?
特别值得一提的是,小蓝可以使用一个魔法,将一件物品的重量增加 K \small K K, 同时价值翻倍。(当然小蓝也可以不使用魔法)
【输入格式】
第一行包含 3 \small3 3 个整数 N 、 M \small N、M N、M 和 K \small K K。
以下 N \small N N 行,每行两个整数 W i \small W_i Wi 和 V i \small V_i Vi。
【输出格式】
一个整数代表答案。
【样例输入】
3 10 3
5 10
4 9
3 8
【样例输出】
26
【样例说明】
选择第二件和第三件物品,同时对第二件物品使用魔法。
【评测用例规模与约定】
对于
30
%
\small 30\%
30% 的数据,
1
≤
N
,
M
,
K
≤
100.
\small 1≤ N,M,K ≤100.
1≤N,M,K≤100.
对于
100
%
\small 100\%
100% 的数据,
1
≤
N
≤
2000
,
1
≤
M
,
K
≤
10000
,
0
≤
W
i
,
V
i
≤
10000.
\small 1≤ N ≤2000,1≤ M,K ≤10000,0≤W_i,V_i ≤10000.
1≤N≤2000,1≤M,K≤10000,0≤Wi,Vi≤10000.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.io.IOException;
public class Main {
public static void main(String[] args) { new Main().run(); }
void run() {
int n = nextInt(), m = nextInt(), k = nextInt();
int[][] dp = new int[2][m + 1];
while (n-- > 0) {
int w = nextInt(), v = nextInt(), wk = w + k, vv = v + v;
for (int i = m; i >= w; --i)
dp[1][i] = max(dp[1][i], dp[1][i - w] + v);
for (int i = m; i >= wk; --i)
dp[1][i] = max(dp[1][i], dp[0][i - wk] + vv);
for (int i = m; i >= w; --i)
dp[0][i] = max(dp[0][i], dp[0][i - w] + v);
}
System.out.println(max(dp[0][m], dp[1][m]));
}
int max(int arg1, int arg2, int arg3) { return max(arg1, max(arg2, arg3)); }
int max(int arg1, int arg2) { return arg1 > arg2 ? arg1 : arg2; }
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
int nextInt() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return (int)in.nval;
}
}
背包模板。
试题 H: 修路
时间限制:3.0s 内存限制:512.0MB 本题总分:20 分
【问题描述】
这天,小明在修路。
他需要修理两条平行的道路 A , B \small A,B A,B,两条路上面分别有 n \small n n 个和 m \small m m 个点需要维修,它们相对于道路起点的距离分别为 a 1 , a 2 , ⋯ , a n \small a_1,a_2,\cdots,a_n a1,a2,⋯,an 和 b 1 , b 2 , ⋯ , b m \small b_1,b_2,\cdots,b_m b1,b2,⋯,bm。如图,两条路之间的距离为 d \small d d 且它们起点 (最左端) 的连线和两条路都垂直。小明的起点为道路 A \small A A 的起点,他需要尽可能快地遍历这些需要维修的 n + m \small n + m n+m 个点,他既可以沿着道路 向右 行走,也可以在两条道路之间的空地上 随意 行走。
小明想知道遍历这些点的最短路程是多少。
【输入格式】
输入共三行,第一行为三个正整数 n , m , d \small n,m,d n,m,d。
第二行为 n \small n n 个由空格隔开的正整数 a 1 , a 2 , ⋯ , a n \small a_1,a_2,\cdots,a_n a1,a2,⋯,an。
第三行为 m \small m m 个由空格隔开的正整数 b 1 , b 2 , ⋯ , b m \small b_1,b_2,\cdots,b_m b1,b2,⋯,bm。
【输出格式】
一行,一个浮点数,表示答案,保留两位小数。
【样例输入】
2 2 2
2 1
1 2
【样例输出】
5.24
【样例说明】
图中红线指出了样例的最短路线, 1 + 1 + 5 + 1 = 5.24 \small 1 + 1 + \sqrt5 + 1 = 5.24 1+1+5+1=5.24。
【评测用例规模与约定】
对于
30
%
\small 30\%
30% 的数据,保证
n
+
m
≤
10
\small n + m≤10
n+m≤10;
对于
100
%
\small 100\%
100% 的数据,保证
n
,
m
≤
2000
,
d
≤
4
×
1
0
6
,
a
i
,
b
i
≤
1
0
6
\small n,m≤2000,d ≤4×10^6,a_i,b_i ≤10^6
n,m≤2000,d≤4×106,ai,bi≤106。
好嘛, 20 20 20 年同组 的原题,题号都不带改的。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.io.IOException;
import java.util.Arrays;
import static java.lang.Math.hypot;
import static java.lang.Math.min;
public class Main {
public static void main(String[] args) { new Main().run(); }
void run() {
int n = nextInt(), m = nextInt(), d = nextInt();
int[] A = new int[n + 1], B = new int[m + 1];
double[][][] dp = new double[2][n + 1][m + 1];
for (int i = 1; i <= n; ++i) A[i] = nextInt();
for (int i = 1; i <= m; ++i) B[i] = nextInt();
Arrays.sort(A, 1, n + 1);
Arrays.sort(B, 1, m + 1);
for (int i = 1; i <= n; ++i) {
dp[1][i][0] = Double.POSITIVE_INFINITY;
dp[0][i][0] = A[i];
}
if (m > 0) {
dp[0][0][1] = Double.POSITIVE_INFINITY;
dp[1][0][1] = hypot(d, B[1]);
dp[1][0][0] = d;
}
for (int i = 2; i <= m; ++i) {
dp[0][0][i] = Double.POSITIVE_INFINITY;
dp[1][0][i] = B[i] - B[i - 1] + dp[1][0][i - 1];
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
dp[0][i][j] = min(dp[0][i - 1][j] + A[i] - A[i - 1], dp[1][i - 1][j] + hypot(d, A[i] - B[j]));
dp[1][i][j] = min(dp[1][i][j - 1] + B[j] - B[j - 1], dp[0][i][j - 1] + hypot(d, A[i] - B[j]));
}
System.out.printf("%.2f", min(dp[0][n][m], dp[1][n][m]));
}
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
int nextInt() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return (int)in.nval;
}
}
出题人脸都不要了。
试题 I: 围栏
时间限制:3.0s 内存限制:512.0MB 本题总分:25 分
【问题描述】
这天,小明在造围栏。
他提前在地上 (二维平面) 打好了 n \small n n 个洞,这 n \small n n 个洞的位置形成了一个凸多边形。当他准备把固定围栏的木杆插进去的时候,突然发现自己少准备了两根木杆。
如图,他现在只能在这 n \small n n 个洞中选出 n − 2 \small n−2 n−2 个来放置木杆,他想知道用这 n − 2 \small n−2 n−2 个木杆能围成的凸多边形的最大的面积是多少。
【输入格式】
输入共 n + 1 \small n + 1 n+1 行,第一行为一个正整数 n \small n n。
后面 n \small n n 行,每行两个整数 x i , y i \small x_i,y_i xi,yi 表示第 i \small i i 个洞的坐标。
保证按照逆时针的顺序输入这 n \bm\small n n 个点的坐标。
【输出格式】
一行,一个正整数,表示答案。
为了避免小数,请输出面积的两倍。
【样例输入】
5
0 0
1 0
2 1
0 3
-1 1
【样例输出】
6
【样例说明】
选择 ( − 1 , 1 ) ( 2 , 1 ) ( 0 , 3 ) \small (−1,1)\:(2,1)\:(0,3) (−1,1)(2,1)(0,3) 这三个点构成的多边形面积最大,为 3 3 3,所以输出 6 6 6。
【评测用例规模与约定】
对于 100 % \small 100\% 100% 的数据,保证 5 ≤ n ≤ 100 ; ∣ x i ∣ , ∣ y i ∣ ≤ 1 0 6 \small 5≤n≤100;|x_i|,|y_i|≤10^6 5≤n≤100;∣xi∣,∣yi∣≤106。
PAT (Top Level) Practice 1023 青春版,网上应该有许多该题 d p \rm dp dp 解法的博客之类的,这里不作赘述直接上代码了。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.io.IOException;
public class Main {
public static void main(String[] args) { new Main().run(); }
void run() {
int n = nextInt();
int[] x = new int[n], y = new int[n];
long[][][] dp = new long[n][n][n];
long ans = 0;
for (int i = 0; i < n; ++i) {
x[i] = nextInt();
y[i] = nextInt();
}
for (int k = 2; k < n - 2; ++k)
for (int i = 0; i < n; ++i)
for (int j = i + k; j < n; ++j)
for (int l = i; l <= j; ++l) {
dp[i][j][k] = max(dp[i][j][k], dp[i][l][k - 1] +
abs((long)(x[j] - x[i]) * (y[l] - y[i]) - (long)(x[l] - x[i]) * (y[j] - y[i])));
ans = max(ans, dp[i][j][k]);
}
System.out.println(ans);
}
long max(long arg1, long arg2) { return arg1 > arg2 ? arg1 : arg2; }
long abs(long arg) { return arg > 0 ? arg : -arg; }
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
int nextInt() {
try {
in.nextToken();
} catch (IOException e) {
e.printStackTrace();
}
return (int)in.nval;
}
}
真脸都不要了。
试题 J: 好数之和
时间限制:3.0s 内存限制:512.0MB 本题总分:25 分
【问题描述】
一个整数如果包含连续的 2022 \small 2022 2022 四个数字,我们就称之为 “ \small “ “好数 ” \small” ”。
例如 2022 、 52022 、 20223 、 7020224 \small 2022、52022、20223、7020224 2022、52022、20223、7020224 都是好数,而 2202 、 20022 、 2222 \small 2202、20022、2222 2202、20022、2222 都不是好数。
给定 L \small L L 和 R \small R R,请你计算在 L \small L L 到 R \small R R 之间(包含 L \small L L 和 R \small R R)所有好数的和是多少?
【输入格式】
两个整数 L \small L L 和 R \small R R 。
【输出格式】
一个整数代表答案。
【样例输入】
1 20000
【样例输出】
14044
【样例说明】
满足条件的好数有 2022 、 12022 \small 2022、12022 2022、12022,它们的和是 14044 \small 14044 14044。
【评测用例规模与约定】
对于
60
%
\small 60\%
60% 的评测用例,
R
−
L
≤
1
0
8
\small R−L≤10^8
R−L≤108
对于
100
%
\small 100\%
100% 的评测用例,
1
≤
L
≤
R
≤
1
0
9
\small 1≤ L≤R≤10^9
1≤L≤R≤109
分段打表
将区间答案拆分成两部分相减是处理计数类问题常见的手段,这启发了我们通过前缀和来处理该问题,并且参数 R R R 的阶相对较小,显然可以在一个理想的时间内计算出所有可能的 L 、 R L、R L、R 对应的前缀和,于是考虑分段打表,先将输入范围拆分成合理的若干块,并计算出块的前缀和。
顺带将程序补全算了。
import java.awt.datatransfer.StringSelection;
import java.awt.Toolkit;
public class Test extends java.io.CharArrayWriter {
public static void main(String[] args) { new Test().run(); }
int N = (int)1e9, M = N / 666;
String target = "2022";
void run() {
append("import java.util.Scanner;\n\n").
append("public class Main {\n\n").
append(" public static void main(String[] args) { new Main().run(); }\n\n").
append(" void run() {\n").
append(" Scanner in = new Scanner(System.in);\n").
append(" int L = in.nextInt(), R = in.nextInt();\n").
append(" System.out.print(calc(R) - calc(L - 1));\n }\n\n").
append(" long[] sum = { 0");
Long sum = 0L;
for (int i = 1; i <= N;) {
for (int j = 1; j < M; ++j, ++i)
if (Integer.toString(i).contains(target)) sum += i;
append(", ").append(sum.toString()).append('L');
}
append(" };\n\n").
append(" long calc(int n) {\n").
append(" long res = sum[n / " + M + "];\n").
append(" for (int i = n / " + M + " * " + M + "; i <= n; ++i)\n").
append(" if (Integer.toString(i).contains(\"" + target + "\")) res += i;\n").
append(" return res;\n }\n}");
Toolkit.getDefaultToolkit().
getSystemClipboard().
setContents(
new StringSelection(this.toString()), null);
}
}
面向编程的编程。
数位 DP
题出的都没啥营养,不想写了。