第十三届蓝桥杯大赛软件赛决赛(Java 大学B组)

蓝桥杯 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. 1N,M10000.
  对于 100 % \small 100\% 100% 的评测用例, 1 ≤ N , M ≤ 200000 , 1 ≤ x ≤ N . \small 1≤ N,M ≤200000,1≤ x≤ N. 1N,M200000,1xN.


懒惰删除法


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) (N1,M1)

  对于窗口的操作有如下 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 3K+2 : : 每行一个操作,格式见题目描述。

【输出格式】

  第 1 ⋯ N \small 1\cdots N 1N : : 每行 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. 1N,M256,1K10000.
  输入数据保证 : :
   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 1PID100000
   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,m20
  对于 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,m2000;xi1,yi1,xi2,yi2n


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 N1 是正品,重量相同;有 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 1N106;
  对于 100 % \small 100\% 100% 的数据, 1 ≤ N ≤ 1 0 9 , 1 ≤ M ≤ 1 0 5 \small 1 ≤ N ≤ 10^9,1 ≤ M ≤ 10^5 1N109,1M105, 参与 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 NM 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. 1N,M,K100.
  对于 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. 1N2000,1M,K10000,0Wi,Vi10000.


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+m10
  对于 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,m2000,d4×106,ai,bi106


  好嘛, 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 n2 个来放置木杆,他想知道用这 n − 2 \small n−2 n2 个木杆能围成的凸多边形的最大的面积是多少。

【输入格式】

  输入共 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 5n100xi,yi106


  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 202252022202237020224 都是好数,而 2202 、 20022 、 2222 \small 2202、20022、2222 2202200222222 都不是好数。

  给定 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 202212022,它们的和是 14044 \small 14044 14044

【评测用例规模与约定】

  对于 60 % \small 60\% 60% 的评测用例, R − L ≤ 1 0 8 \small R−L≤10^8 RL108
  对于 100 % \small 100\% 100% 的评测用例, 1 ≤ L ≤ R ≤ 1 0 9 \small 1≤ L≤R≤10^9 1LR109


分段打表


  将区间答案拆分成两部分相减是处理计数类问题常见的手段,这启发了我们通过前缀和来处理该问题,并且参数 R R R 的阶相对较小,显然可以在一个理想的时间内计算出所有可能的 L 、 R L、R LR 对应的前缀和,于是考虑分段打表,先将输入范围拆分成合理的若干块,并计算出块的前缀和。

  顺带将程序补全算了。

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


  题出的都没啥营养,不想写了。

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值