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


  艹,省赛梦游赛区第一丢了,

  吃完午饭出食堂伞也被偷了,

  我 r 你哥。


试题 A: 星期计算

本题总分: 5 5 5


【问题描述】

  已知今天是星期六,请问 2 0 22 20^{22} 2022 天后是星期几?

  注意用数字 1 1 1 7 7 7 表示星期一到星期日。

【答案提交】

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


7


  快速幂求出 2 0 22 20^{22} 2022 7 7 7 的余数就行,它的意义为 2 0 22 20^{22} 2022 天后是某个星期六后的第几天。

  用 BigInteger 累乘或者换 P y t h o n \mathrm{Python} Python 上阵都可以,

  但作为签到题,其门槛比 C B \mathrm{CB} CB 组别的要高,

  我是不大能理解的。

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int offset = 1, a = 20, b = 22, p = 7;

    void run() {
        while (b > 0) {
            if (b % 2 == 1)
                offset = offset * a % p;
            a = a * a % p;
            b /= 2;
        }
        System.out.print(6 + offset);
    }
}

试题 B: 山

本题总分: 5 5 5


【问题描述】

  这天小明正在学数数。

  他突然发现有些正整数的形状像一座“山”,比如 123565321 123565321 123565321 145541 145541 145541,它们左右对称(回文)且数位上的数字先单调不减,后单调不增。

  小明数了很久也没有数完,他想让你告诉他在区间 [ [ [ 2022 2022 2022 , , , 2022222022 2022222022 2022222022 ] ] ] 中有多少个数的形状像一座“山”。

【答案提交】

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


3138


  任意一个字串 S S S,都能唯一确定两个回文串 S   S ‾ S\ \overline S S S S   S [ 1 , ∣ S ∣ − 1 ] ‾ S\ \overline{S[1,|S|-1]} S S[1,S1],其中 S ‾ \overline S S 表示对 S S S 翻转, S [ i , j ] S[i,j] S[i,j] 表示从 [ i , j ] [i,j] [i,j] 的字符构成的子串。

  例如 A B C \mathrm{ABC} ABC 就能确定出 A B C C B A \mathrm{ABCCBA} ABCCBA A B C B A \mathrm{ABCBA} ABCBA

  设 f i , j f_{i,j} fi,j [ i , j ] [i,j] [i,j] 中满足题目所述条件的数字个数,将 f 2021 , 2022222022 f_{2021,2022222022} f2021,2022222022 拆分为表达式 f 1 , 1999999999 + f 2000000000 , 2022222022 − f 1 , 2021 − 1 f_{1,1999999999} + f_{2000000000, 2022222022}-f_{1,2021-1} f1,1999999999+f2000000000,2022222022f1,20211

  将第一项 f 1 , 1999999999 f_{1,1999999999} f1,1999999999 用上述性质计算,其余两项暴力求解,即可在一个理想的时间内计算出答案。

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int ans = 0, start = 2022, end = 2022222022;

    void run() {
        for (int i = 1; i < 100000; ++i)
            if (check2(i)) {
                if (i <= 20000) ++ans;
                ++ans;
            }
        System.out.println(ans + calc(2000000000, end) - calc(1, start - 1));
    }

    int calc(int start, int end) {
        int cnt = 0;
        while (start <= end)
            if (check1(start++)) ++cnt;
        return cnt;
    }

    int[] buff = new int[64];

    boolean check1(int k) {
        int n = 0;
        for (; k > 0; k /= 10)
            buff[n++] = k % 10;
        buff[n] = 0;
        for (int i = n - 1, j = 0; i >= j; --i, ++j)
            if (buff[i + 1] >  buff[i] || buff[i] != buff[j]) return false;
        return true;
    }

    boolean check2(int k) {
        int n = 0;
        for (; k > 0; k /= 10)
            buff[n++] = k % 10;
        buff[n] = 0;
        for (int i = n - 1; i >= 0; --i)
            if (buff[i + 1] >  buff[i]) return false;
        return true;
    }
}

试题 C: 字符统计

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 10 10 10


【问题描述】

  给定一个只包含大写字母的字符串 S S S,请你输出其中出现次数最多的字母。

  如果有多个字母均出现了最多次,按字母表顺序依次输出所有这些字母。

【输入格式】

  一个只包含大写字母的字符串 S S S

【输出格式】

  若干个大写字母,代表答案。

【样例输入】

BABBACAC

【样例输出】

AB

【评测用例规模与约定】

  对于 100 % 100\% 100% 的评测用例, 1 ≤ ∣ S ∣ ≤ 1 0 6 1 ≤ |S | ≤ 10^6 1S106


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintWriter;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    int ans, cnt[] = new int[0x7F];

    void run() {
        try(BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)) {
            String line = in.readLine();
            for (int i = line.length(); i > 0;)
                ans = Math.max(ans, ++cnt[line.charAt(--i)]);
            for (char c = 'A'; c <= 'Z'; ++c)
                if (cnt[c] == ans) out.write(c);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  签到题,但 Java 组在这里可以先把快读快写实现一下,说不定后面会用到。


试题 D: 最少刷题数

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 10 10 10


【问题描述】

  小蓝老师教的编程课有 N N N 名学生,编号依次是 1 ⋯ N 1\cdots N 1N。第 i i i 号学生这学期刷题的数量是 A i A_i Ai

  对于每一名学生,请你计算他至少还要再刷多少道题,才能使得全班刷题比他多的学生数不超过刷题比他少的学生数。

【输入格式】

  第一行包含一个正整数 N N N

  第二行包含 N N N 个整数 : A 1 , A 2 , A 3 , ⋯   , A N :A_1, A_2, A_3,\cdots, A_N A1,A2,A3,,AN

【输出格式】

  输出 N N N 个整数,依次表示第 1 ⋯ N 1\cdots N 1N 号学生分别至少还要再刷多少道题。

【样例输入】

5
12 10 15 20 6

【样例输出】

0 3 0 0 7

【评测用例规模与约定】

  对于 30 % 30\% 30% 的数据, 1 ≤ N ≤ 1000 , 0 ≤ A i ≤ 1000 1 ≤ N ≤ 1000, 0 ≤ A_i ≤ 1000 1N1000,0Ai1000
  对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100000 , 0 ≤ A i ≤ 100000 1 ≤ N ≤ 100000, 0 ≤ A_i ≤ 100000 1N100000,0Ai100000


  求中位数,有两个细节需要注意:

  一、中位数必须为序列 A A A 中的某个确定的数。
  二、当一个小于中位数的学生刷题量提升时,小于中位数的学生会减少一个,因此要特判一下中位数前后缀和相等的情况。

import java.io.*;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    int N = 100000, arg1, arg2;

    int[] cnt = new int[N + 1];

    void run() {
        PrintWriter out = new PrintWriter(System.out);
        int n = nextInt();
        int[] A = new int[n];
        for (int i = 0; i < n; ++i)
            ++cnt[A[i] = nextInt()];
        for (int i = 0; i <= N; ++i)
            if (cnt[i] > 0){
                if (arg1 >= n - cnt[i] - arg1) {
                    arg1 = arg1 == n - cnt[i] - arg1 ? i + 1: i;
                    arg2 = i;
                    break;
                }
                arg1 += cnt[i];
            }
        for (int i = 0; i < n; ++i) {
            out.print(A[i] >= arg2 ? 0 : arg1 - A[i]);
            out.write(' ');
        }
        out.flush();
    }

    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    int nextInt() {
        try {
            in.nextToken();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (int)in.nval;
    }
}

试题 E: 求阶乘

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 15 15 15


【问题描述】

  满足 N ! N! N! 的末尾恰好有 K K K 0 0 0 的最小的 N N N 是多少?

  如果这样的 N N N 不存在输出 − 1 −1 1

【输入格式】

  一个整数 K K K

【输出格式】

  一个整数代表答案。

【样例输入】

2

【样例输出】

10

【评测用例规模与约定】

  对于 30 % 30\% 30% 的数据, 1 ≤ K ≤ 1 0 6 1 ≤ K ≤ 10^6 1K106
  对于 100 % 100\% 100% 的数据, 1 ≤ K ≤ 1 0 18 1 ≤ K ≤ 10^{18} 1K1018


  如何计算 N ! N! N! 的某个大于 1 1 1 的因子的值数?

   f ( N , a ) = ∑ i = 1 ⌊ log ⁡ a N ⌋ ⌊ N a i ⌋ \displaystyle{f(N,a) = \sum_{i=1}^{\lfloor\log_aN\rfloor}\left\lfloor\frac N{a^i}\right\rfloor} f(N,a)=i=1logaNaiN

  上式的意义是,我们对 1 ∼ N 1\sim N 1N 中每个包含 a i a^i ai 的数统计一次,若 1 ∼ N 1\sim N 1N 中的某个数能表示为 m a k ma^k mak m , k ∈ Z + m,k\in\mathbb Z^+ m,kZ+ k k k 尽可能的大,则 m a k ma^k mak 只会恰被统计 k k k 次。

  通过上式我们也能知道, f ( N , 2 ) ≥ f ( N , 5 ) f(N,2) \geq f(N,5) f(N,2)f(N,5) N ∈ N N\in\mathbb N NN 时处处成立,而 10 10 10 只能被表示为 2 × 5 2\times5 2×5,故 N ! N! N! 后缀 0 0 0 的个数等于 5 5 5 的指数。

  总之,一眼二分。

import java.util.Scanner;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    void run() {
        long K = new Scanner(System.in).nextLong();
        long L = 5, R = K * 5, M, T = 0;
        while (L <= R) {
            M = L + R >> 1; T = 0;
            for (long I = 5; I <= M; I *= 5) T += M / I;
            if (T == K) {
                System.out.println(M - M % 5);
                return;
            }
            if (T < K) L = M + 1; else R = M - 1;
        }
        System.out.println("-1");
    }
}

试题 F: 最大子矩阵

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 15 15 15


【问题描述】

  小明有一个大小为 N × M N × M N×M 的矩阵,可以理解为一个 N N N M M M 列的二维数组。我们定义一个矩阵 m m m 的稳定度 f ( m ) f(m) f(m) f ( m ) = max ⁡ ( m ) − min ⁡ ( m ) f(m) = \max(m) − \min(m) f(m)=max(m)min(m),其中 max ⁡ ( m ) \max(m) max(m) 表示矩阵 m m m 中的最大值, min ⁡ ( m ) \min(m) min(m) 表示矩阵 m m m 中的最小值。现在小明想要从这个矩阵中找到一个稳定度不大于 l i m i t limit limit 的子矩阵,同时他还希望这个子矩阵的面积越大越好(面积可以理解为矩阵中元素个数)。

  子矩阵定义如下:从原矩阵中选择一组连续的行和一组连续的列,这些行列交点上的元素组成的矩阵即为一个子矩阵。

【输入格式】

  第一行输入两个整数 N , M N,M NM,表示矩阵的大小。

  接下来 N N N 行,每行输入 M M M 个整数,表示这个矩阵。

  最后一行输入一个整数 l i m i t limit limit,表示限制。

【输出格式】

  输出一个整数,分别表示小明选择的子矩阵的最大面积。

【样例输入】

3 4
2 0 7 9
0 6 9 7
8 4 6 4
8

【样例输出】

6

【样例说明】
  满足稳定度不大于 8 8 8 的且面积最大的子矩阵总共有三个,他们的面积都是 6 6 6 (粗体表示子矩阵元素) : :

   2   0   7   9 \mathbf2\ \mathbf0\ 7\ 9 2 0 7 9
   0   6   9   7 \mathbf0\ \mathbf6\ 9\ 7 0 6 9 7
   8   4   6   4 \mathbf8\ \mathbf4\ 6\ 4 8 4 6 4

   2   0   7   9 2\ 0\ \mathbf7\ \mathbf9 2 0 7 9
   0   6   9   7 0\ 6\ \mathbf9\ \mathbf7 0 6 9 7
   8   4   6   4 8\ 4\ \mathbf6\ \mathbf4 8 4 6 4

   2   0   7   9 2\ 0\ 7\ 9 2 0 7 9
   0   6   9   7 0\ \mathbf6\ \mathbf9\ \mathbf7 0 6 9 7
   8   4   6   4 8\ \mathbf4\ \mathbf6\ \mathbf4 8 4 6 4

【评测用例规模与约定】

在这里插入图片描述
  对于所有评测用例, 0 ≤ 0 ≤ 0 矩阵元素值, l i m i t ≤ 1 0 5 limit ≤ 10^5 limit105


优先队列


  很常见的题型,我们一般用两点表示一个可能为最终答案的矩阵的左下角和右上角,常见一种解题的方式就是枚举一点,然后再枚举另一点,确定一个矩阵然后更新答案,这样穷举了所有子矩阵,复杂度在 O ( n 2 m 2 β ( A ) ) O(n^2m^2\beta(A)) O(n2m2β(A)),其中 β ( A ) \beta(A) β(A) 为确定一个矩阵所需要的时间。

  这个题中,矩阵的信息为最值,这恰是目前信息学届没有什么高效解决方案的板块,但考虑到 n n n 的阶很小,故我们枚举两点中的 x 1 、 x 2 x_1、x_2 x1x2,然后穷举 y 1 、 y 2 y_1、y_2 y1y2,这样做只需要 O ( m n 2 ) O(mn^2) O(mn2) 的预处理,就能 O ( 1 ) O(1) O(1) 确定一列的最值,但穷举 y 1 、 y 2 y_1、y_2 y1y2 O ( m 2 ) O(m^2) O(m2) 的复杂度实在另人难以接受,于是考虑优先队列优化,

  建立两个先排最值后排序号的优先队列,顺序枚举 y 2 y_2 y2,我们将 ( x 1 , y 2 ) 、 ( x 2 , y 2 ) (x_1,y_2)、(x_2,y_2) (x1,y2)(x2,y2) 间的最大值记为 m a x max max,显然以 x 1 、 x 2 、 y 2 x_1、x_2、y_2 x1x2y2 确定的矩形中,值小于 m a x max max 不会影响矩阵的合法性,故将其移除队列,最小值也是同理,然后将 y 2 y_2 y2 分别插入两个队列中,

  此时队列中全是大于 m a x max max 和小于 m i n min min 的列,若此时队头元素之差不合法,则常数则将小于等于 y 1 y_1 y1 的元素移除队列,并将 y 1 y_1 y1 1 1 1 来维护合法性。

  有时间我再重新组织一下,主要队列做的不是最优,而是合法,有点不好给出一个系统的描述方式。

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(); }

    void run() {
        int n = nextInt(), m = nextInt();
        int[][][] max = new int[m][n][n];
        int[][][] min = new int[m][n][n];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                max[j][i][i] = min[j][i][i] = nextInt();
        int lim = nextInt(), ans = 0, maxN, minN;
        for (int k = 0; k < m; ++k)
            for (int i = 0; i < n; ++i)
                for (int j = i + 1; j < n; ++j) {
                    max[k][i][j] = max(max[k][i][j - 1], max[k][j][j]);
                    min[k][i][j] = min(min[k][i][j - 1], min[k][j][j]);
                }

        for (int i1 = 0; i1 < n; ++i1)
            for (int i2 = i1; i2 < n; ++i2) {
                int I1 = i1, I2 = i2;
                Queue<Integer> maxQ = new PriorityQueue((a, b)->Integer.compare(max[(int)b][I1][I2], max[(int)a][I1][I2]));
                Queue<Integer> minQ = new PriorityQueue((a, b)->Integer.compare(min[(int)a][I1][I2], min[(int)b][I1][I2]));
                for (int l = 0, r = 0; r < m; ++r) {
                    if (max[r][i1][i2] - min[r][i1][i2] > lim) {
                        maxQ.clear();
                        minQ.clear();
                        l = r + 1;
                        continue;
                    }
                    while (!maxQ.isEmpty() && max[maxQ.peek()][i1][i2] <= max[r][i1][i2]) maxQ.poll();
                    while (!minQ.isEmpty() && min[minQ.peek()][i1][i2] >= min[r][i1][i2]) minQ.poll();
                    maxQ.offer(r);
                    minQ.offer(r);
                    while (max[maxQ.peek()][i1][i2] - min[minQ.peek()][i1][i2] > lim) {
                        while (maxQ.peek() <= l) maxQ.poll();
                        while (minQ.peek() <= l) minQ.poll();
                        ++l;
                    }
                    ans = max(ans, (r - l + 1) * (i2 - i1 + 1));
                }
            }
        System.out.println(ans);
    }

    int min(int arg1, int arg2) { return arg1 < arg2 ? arg1 : arg2; }

    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;
    }
}

试题 G: 数组切分

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 20 20 20


【问题描述】

  已知一个长度为 N N N 的数组 : A 1 , A 2 , A 3 , :A_1, A_2, A_3, A1,A2,A3, ⋯ \cdots A N A_N AN 恰好是 1 ∼ N 1 \sim N 1N 的一个排列。现在要求你将 A A A 数组切分成若干个 (最少一个,最多 N N N 个) 连续的子数组,并且每个子数组中包含的整数恰好可以组成一段连续的自然数。

  例如对于 A = { 1 , 3 , 2 , 4 } A = \{1, 3, 2, 4\} A={1,3,2,4}, 一共有 5 5 5 种切分方法 : :

   { 1 } { 3 } { 2 } { 4 } : \{1\}\{3\}\{2\}\{4\}: {1}{3}{2}{4}每个单独的数显然是 (长度为 1 1 1 的) 一段连续的自然数

   { 1 } { 3 , 2 } { 4 } : \{1\}\{3, 2\}\{4\}: {1}{3,2}{4} { 3 , 2 } \{3, 2\} {3,2} 包含 2 2 2 3 3 3,是 一段连续的自然数,另外 { 1 } \{1\} {1} { 4 } \{4\} {4} 显然也是。

   { 1 } { 3 , 2 , 4 } : \{1\}\{3, 2, 4\}: {1}{3,2,4} { 3 , 2 , 4 } \{3, 2, 4\} {3,2,4} 包含 2 2 2 4 4 4,是 一段连续的自然数,另外 { 1 } \{1\} {1} 显然也是。

   { 1 , 3 , 2 } { 4 } : \{1, 3, 2\}\{4\}: {1,3,2}{4} { 1 , 3 , 2 } \{1, 3, 2\} {1,3,2} 包含 1 1 1 3 3 3,是 一段连续的自然数,另外 { 4 } \{4\} {4} 显然也是。

   { 1 , 3 , 2 , 4 } : \{1, 3, 2, 4\}: {1,3,2,4}只有一个子数组,包含 1 到 4,是 一段连续的自然数

【输入格式】

  第一行包含一个整数 N N N

  第二行包含 N N N 个整数,代表 A A A 数组。

【输出格式】

  输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 1000000007 1000000007 取模后的值。

【样例输入】

4
1 3 2 4

【样例输出】

5

【评测用例规模与约定】

  对于 30 % 30\% 30% 评测用例, 1 ≤ N ≤ 20 1 ≤ N ≤ 20 1N20
  对于 100 % 100\% 100% 评测用例, 1 ≤ N ≤ 10000 1 ≤ N ≤ 10000 1N10000


动态规划


  题目给定的序列 A A A 是某个 N N N 级排列,故而对于任意 i , j i,j i,j 1 ≤ i , j ≤ N 1 \leq i,j \leq N 1i,jN,都有 A i ∈ N A_i\in\mathbb N AiN,且 A i ≠ A j A_i \neq A_j Ai=Aj

  给出推论,若 A A A 的某个子序列 A [ i , j ] A[i,j] A[i,j] 能重排成一段连续的自然数,当且仅当 A [ i , j ] A[i,j] A[i,j] 满足 j − i = max ⁡ { A [ i , j ] } − min ⁡ { A [ i , j ] } j - i = \max\{A[i,j]\} - \min\{A[i,j]\} ji=max{A[i,j]}min{A[i,j]}

  略证:

  若 A [ i , j ] A[i,j] A[i,j] 满足 j − i = max ⁡ { A [ i , j ] } − min ⁡ { A [ i , j ] } j - i = \max\{A[i,j]\} - \min\{A[i,j]\} ji=max{A[i,j]}min{A[i,j]},根据排列的定义,满足 min ⁡ { A [ i , j ] } ≤ A k ≤ max ⁡ { A [ i , j ] } \min\{A[i,j]\} \leq A_k \leq \max\{A[i,j]\} min{A[i,j]}Akmax{A[i,j]} k k k 只能有 max ⁡ { A [ i , j ] } − min ⁡ { A [ i , j ] } + 1 \max\{A[i,j]\} - \min\{A[i,j]\} + 1 max{A[i,j]}min{A[i,j]}+1 个,所以若等式成立,这 k k k 个数就恰填满 i i i j j j 这个区间,若不成立则一定存在一个 k k k,没有在 i i i j j j 中出现过, A [ i , j ] A[i,j] A[i,j] 按自然序重排后至少在 A k A_k Ak 这一点上不连续。 □ \square

  设 f i f_i fi 表示 A A A 的子序列 A [ 1 , i ] A[1,i] A[1,i] 被切分的方案数,有状态转移方程: f i = ∑ j = 1 i [ i − j = max ⁡ { A [ i , j ] } − min ⁡ { A [ i , j ] } ] f j ( m o d 1000000007 ) f_{i} = \sum_{j=1}^i[i - j = \max\{A[i,j]\} - \min\{A[i,j]\}]f_j \pmod {1000000007} fi=j=1i[ij=max{A[i,j]}min{A[i,j]}]fj(mod1000000007)  最外层的 [ ] [] [] 表示,若 [ ] [] [] 等式成立,则值为 1 1 1,否则为 0 0 0

  初始时 f 0 = 1 f_0 = 1 f0=1,最终答案为 f n f_n fn

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(); }

    int p = 1000000007;

    void run() {
        int n = nextInt(), max, min;
        int[] A = new int[n + 1];
        int[] f = new int[n + 1];
        f[0] = 1;
        for (int i = 1; i <= n; ++i) {
            max = min = A[i] = nextInt();
            for (int j = i; j > 0; --j) {
                if (A[j] > max) max = A[j];
                if (A[j] < min) min = A[j];
                if (i - j == max - min)
                    f[i] = (f[i] + f[j - 1]) % p;
            }
        }
        System.out.println(f[n]);
    }

    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: 回忆迷宫

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 20 20 20


【问题描述】

  爱丽丝刚从一处地下迷宫中探险归来,你能根据她对于自己行动路径的回忆,帮她画出迷宫地图吗?

  迷宫地图是基于二维网格的。爱丽丝会告诉你一系列她在迷宫中的移动步骤,每个移动步骤可能是上下左右四个方向中的一种,表示爱丽丝往这个方向走了一格。你需要根据这些移动步骤给出一个迷宫地图,并满足以下条件 : :

   1 、 1、 1爱丽丝能在迷宫内的某个空地开始,顺利的走完她回忆的所有移动步骤。

   2 、 2、 2迷宫内不存在爱丽丝没有走过的空地。

   3 、 3、 3迷宫是封闭的,即可通过墙分隔迷宫内与迷宫外。任意方向的无穷远处视为迷宫外,所有不与迷宫外联通的空地都视为是迷宫内。(迷宫地图为四联通,即只有上下左右视为联通)

   4 、 4、 4在满足前面三点的前提下,迷宫的墙的数量要尽可能少。

【输入格式】

  第一行一个正整数 N N N,表示爱丽丝回忆的步骤数量。

  接下来一行 N N N 个英文字符,仅包含 U D L R \mathbf{UDLR} UDLR 四种字符,分别表示上 ( U p ) (\mathrm{Up}) Up、下 ( D o w n ) (\mathrm{Down}) Down、左 ( L e f t ) (\mathrm{Left}) Left、右 ( R i g h t ) (\mathrm{Right}) Right

【输出格式】

  请通过字符画的形式输出迷宫地图。迷宫地图可能包含许多行,用字符 ‘ ‘ * ’ ’ 表示墙,用 ‘ ‘ ’ ’ (空格)表示非墙。

  你的输出需要保证以下条件:

   1 、 1、 1至少有一行第一个字符为 ‘ ‘ * ’ ’
   2 、 2、 2第一行至少有一个字符为 ‘ ‘ * ’ ’
   3 、 3、 3每一行的最后一个字符为 ‘ ‘ * ’ ’
   4 、 4、 4最后一行至少有一个字符为 ‘ ‘ * ’ ’

【样例输入】

17
UUUULLLLDDDDRRRRU

【样例输出】

 *****
*     *
* *** *
* *** *
* *** *
*     *
 *****

【样例解释】
  爱丽丝可以把第六行第六个字符作为起点。

 外墙墙墙墙墙外
 墙内内内内内墙
 墙内墙墙墙内墙
 墙内墙墙墙内墙
 墙内墙墙墙内墙
 墙内内内内内墙
 外墙墙墙墙墙外

【评测用例规模与约定】

  对于所有数据, 0 < N ≤ 100 0 < N ≤ 100 0<N100


  H 题出个大模拟?想不出题了可以咬打火机找灵感。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    int N = 333, M = 333, x = 111, y = 111, x1 = x, y1 = y, x2 = x, y2 = y;

    int[][] offset = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

    byte[][] ans = new byte[N][M];

    void run() {
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < M; ++j) ans[i][j] = '$';
        try(BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)) {
            int n = Integer.parseInt(in.readLine());
            String line = in.readLine();
            step(x, y);
            for (int i = 0; i < n; ++i) {
                switch (line.charAt(i)) {
                    case 'U': step(--x, y); break;
                    case 'R': step(x, ++y); break;
                    case 'D': step(++x, y); break;
                    case 'L': step(x, --y);
                }
                if (x < x1) x1 = x;
                if (y < y1) y1 = y;
                if (x > x2) x2 = x;
                if (y > y2) y2 = y;
            }
            Deque<Long> queue = new ArrayDeque();
            queue.offer(makePair(0, 0));
            while (!queue.isEmpty()) {
                int x = X(queue.peek());
                int y = Y(queue.peek());
                queue.poll();
                if (x < 0 || x >= N || y < 0 || y >= M || ans[x][y] != '$') continue;
                ans[x][y] = ' ';
                for (int i = 0; i < 4; ++i)
                    queue.offer(makePair(x + offset[i][0], y + offset[i][1]));
            }
            --x1; ++x2; --y1; ++y2;
            for (; x1 <= x2; ++x1) {
                int space = 0;
                for (int y = y1; y <= y2; ++y)
                    if (ans[x1][y] == ' ') ++space;
                    else {
                        for (; space > 0; --space)
                            out.write(' ');
                        out.write('*');
                    }
                out.write('\n');
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    long makePair(int x, int y) { return (long)x << 32 | y; }

    int X(long pair) { return (int)(pair >>> 32); }

    int Y(long pair) { return (int)pair; }

    void step(int x, int y) {
        ans[x][y] = ' ';
        for (int i = 0; i < 4; ++i)
            if (ans[x + offset[i][0]][y + offset[i][1]] == '$')
                ans[x + offset[i][0]][y + offset[i][1]] = '*';
    }
}

试题  I: 红绿灯

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 512.0 M B 512.0\mathrm{MB} 512.0MB 本题总分: 25 25 25


【问题描述】

  爱丽丝要开车去上班,上班的路上有许多红绿灯,这让爱丽丝很难过。为了上班不迟到,她给自己的车安装了氮气喷射装置。现在她想知道自己上班最短需要多少时间。

  爱丽丝的车最高速度是 1 V \frac 1V V1 米每秒,并且经过改装后,可以瞬间加速到小于等于最高速的任意速度,也可以瞬间停止。

  爱丽丝家离公司有 N N N 米远,路上有 M M M 个红绿灯,第 i i i 个红绿灯位于离爱丽丝家 A i A_i Ai 米远的位置,绿灯持续 B i B_i Bi 秒,红灯持续 C i C_i Ci 秒。在初始时(爱丽丝开始计时的瞬间),所有红绿灯都恰好从红灯变为绿灯。如果爱丽丝在绿灯变红的瞬间到达红绿灯,她会停下车等红灯,因为她是遵纪守法的好市民。

  氮气喷射装置可以让爱丽丝的车瞬间加速到超光速(且不受相对论效应的影响!),达到瞬移的效果,但是爱丽丝是遵纪守法的好市民,在每个红绿灯前她都会停下氮气喷射,即使是绿灯,因为红绿灯处有斑马线,而使用氮气喷射装置通过斑马线是违法的。此外,氮气喷射装置不能连续启动,需要一定时间的冷却,表现为通过 K K K 个红绿灯后才能再次使用。(也就是说,如果 K = 1 K = 1 K=1,就能一直使用啦!)初始时,氮气喷射装置处于可用状态。

【输入格式】

  第一行四个正整数 N 、 M 、 K 、 V N、M、K、V NMKV,含义如题面所述。

  接下来 M M M 行,每行三个正整数 A i 、 B i 、 C i A_i、B_i、C_i AiBiCi,含义如题面所述。

【输出格式】

  输出一个正整数 T T T,表示爱丽丝到达公司最短需要多少秒。

【样例输入】

90 2 2 2
30 20 20
60 20 20

【样例输出】

80

【样例解释】
  爱丽丝在最开始直接使用氮气喷射装置瞬间到达第一个红绿灯,然后绿灯通过,以最高速行进 60 60 60 秒后到达第二个红绿灯,此时绿灯刚好变红,于是她等待 20 20 20 秒再次变为绿灯后通过该红绿灯,此时氮气喷射装置冷却完毕,爱丽丝再次使用瞬间到达公司,总共用时 80 80 80 秒。

【评测用例规模与约定】

  对于 30 % 30\% 30% 的数据, N ≤ 100 ; M ≤ 10 ; M < K ; V = 1 N ≤ 100; M ≤ 10; M < K; V = 1 N100;M10;M<K;V=1.
  对于 60 % 60\% 60% 的数据, N ≤ 1000 ; M ≤ 100 ; K ≤ 50 ; B i , C i ≤ 100 ; V ≤ 10 N ≤ 1000; M ≤ 100; K ≤ 50; B_i,C_i ≤ 100; V ≤ 10 N1000;M100;K50;Bi,Ci100;V10.
  对于 100 % 100\% 100% 的数据, 0 < N ≤ 1 0 8 ; M ≤ 1000 ; K ≤ 1000 ; 0 < B i , C i ≤ 1 0 6 ; 0 < V ≤ 1 0 6 ; 0 < A i < N ; 0 < N ≤ 10^8; M ≤ 1000; K ≤ 1000; 0 < B_i,C_i ≤ 10^6; 0 < V ≤ 10^6; 0 < A_i < N; 0<N108;M1000;K1000;0<Bi,Ci106;0<V106;0<Ai<N; 对任意 i < j i < j i<j, 有 A i < A j A_i < A_j Ai<Aj


  动规,但 dotcpp 上卡了一个点,再琢磨琢磨吧。

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(), v = nextInt();
        int[] A = new int[m + 2], B = new int[m + 1], C = new int[m + 1];
        long[][] dp = new long[m + 1][k + 1];
        for (int i = 1; i <= m; ++i) {
            A[i] = nextInt();
            B[i] = nextInt();
            C[i] = nextInt();
        }
        A[m + 1] = n;
        for (int i = 0; i < k; ++i)
            dp[0][i] = A[1] * (long)v;
        for (int i = 1; i <= m; ++i) {
            dp[i][k] = min(dp[i - 1][0], dp[i - 1][1]);
            if (dp[i][k] % (B[i] + C[i]) >= B[i])
                dp[i][k] += B[i] + C[i] - dp[i][k] % (B[i] + C[i]);
            dp[i][0] = dp[i][k] + (A[i + 1] - A[i]) * (long)v;
            for (int j = 1; j < k; ++j) {
                dp[i][j] = dp[i - 1][j + 1] + (A[i + 1] - A[i]) * (long)v;
                if (dp[i - 1][j + 1] % (B[i] + C[i]) >= B[i])
                    dp[i][j] += B[i] + C[i] - dp[i - 1][j + 1] % (B[i] + C[i]);
                dp[i][j] = min(dp[i][j], dp[i][j - 1]);
            }
        }
        long ans = dp[m][0];
        for (int i = 1; i <= k; ++i)
            ans = min(ans, dp[m][i]);
        System.out.println(ans);
    }

    long min(long arg1, long 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;
    }
}

试题 J: 拉箱子

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 1.0 G B 1.0\mathrm{GB} 1.0GB 本题总分: 25 25 25


【问题描述】

  推箱子是一款经典电子游戏,爱丽丝很喜欢玩,但是她有点玩腻了,现在她想设计一款拉箱子游戏。

  拉箱子游戏需要玩家在一个 N × M N × M N×M 的网格地图中,控制小人上下左右移动,将箱子拉到终点以获得胜利。

  现在爱丽丝想知道,在给定地形(即所有墙的位置)的情况下,有多少种不同的可解的初始局面。

  【初始局面】 的定义如下 : :

   1 、 1、 1初始局面由排列成 N × M N × M N×M 矩形网格状的各种元素组成,每个网格中有且只有一种元素。可能的元素有 : : 空地、墙、小人、箱子、终点。
   2 、 2、 2初始局面中有且只有一个小人。
   3 、 3、 3初始局面中有且只有一个箱子。
   4 、 4、 4初始局面中有且只有一个终点。

  【可解】 的定义如下 : :

  通过有限次数的移动小人(可以在移动的同时拉箱子),箱子能够到达终点所在的网格。

  【移动】 的定义如下 : :

  在一次移动中,小人可以移动到相邻(上、下、左、右四种选项)的一个网格中,前提是满足以下条件 : :
   1 、 1、 1小人永远不能移动到 N × M N × M N×M 的网格外部。
   2 、 2、 2小人永远不能移动到墙上或是箱子上。
   3 、 3、 3小人可以移动到空地或是终点上。

  【拉箱子】 的定义如下 : :

  在一次合法移动的同时,如果小人初始所在网格沿小人移动方向的反方向上的相邻网格上恰好是箱子,小人可以拉动箱子一起移动,让箱子移动到小人初始所在网格。
  即使满足条件,小人也可以只移动而不拉箱子。

【输入格式】

  第一行两个正整数 N N N M M M,表示网格的大小。

  接下来 N N N 行,每行 M M M 个由空格隔开的整数 0 0 0 1 1 1 描述给定的地形。其中 1 1 1 表示墙, 0 0 0 表示未知的元素,未知元素可能是小人或箱子或空地或终点,但不能是墙。

【输出格式】

  输出一个正整数,表示可解的初始局面数量。

【样例输入】

2 4
0 0 0 0
1 1 1 0

【样例输出】

13

【样例解释】
   13 13 13 种可解的初始局面示意图如下 : :

 人终箱空
 墙墙墙空
 **********
 人终空箱
 墙墙墙空
 **********
 人空终箱
 墙墙墙空
 **********
 箱人终空
 墙墙墙空
 **********
 空人终箱
 墙墙墙空
 **********
 箱终人空
 墙墙墙空
 **********
 空终人箱
 墙墙墙空
 **********
 箱终空人
 墙墙墙空
 **********
 箱空终人
 墙墙墙空
 **********
 空箱终人
 墙墙墙空
 **********
 箱终空空
 墙墙墙人
 **********
 箱空终空
 墙墙墙人
 **********
 空箱终空
 墙墙墙人

【评测用例规模与约定】

  对于 30 % 30\% 30% 的数据, N , M ≤ 3 N, M ≤ 3 N,M3
  对于 100 % 100\% 100% 的数据, 0 < N , M ≤ 10 0 < N, M ≤ 10 0<N,M10


大模拟


  J 题也出个大模拟?

  题出的很好,下次别出了。

  简单回看了一下代码量,由于笔者不喜欢在代码上添加注释,所以这里简单概况一下思路,以及对变量的作用做出一定的解释。

  对于箱子在第 i i i j j j 列,人摆放可以按某个位置能否到达箱子的某个方向来分为四种情况,而终点的摆放就是人从箱子的某个方向开始,可达的所有方格,不过人箱终不可重叠,故而我们在统计终点时要记录出这个点是否可达起点,在统计答案时减去人箱终重叠的情况。

  但朴素的去搜索复杂度过高,且难以预估,于是我们预处理出color[i][j][k][g]数组,表示地图上第ij列的方格不可达时,第kg列的方格的颜色。

  更具体地说,我们枚举每一对i j,以及四个方向,若从i j确定的方格的某个方向上的第一个方格没有颜色,我们以它为起点广搜染色,否则跳过此轮循环。

  所以我们可以通过color[i][j][x1][y1]是否等于color[i][j][x2][y2]来判断不经过i j确定的方格x1 y1x2 y2确定的方格之间是否可达。

  于是在搜索终点时,我们有四个分支可选:

  一、 小人沿当前方向前进一步
  其他、小人尝试往箱子的另一个方向移动

  设当前箱子在i j,小人紧贴箱子在 x y,若箱子其他方向上相邻的一个格子为k g,若color[i][j][x][y]等于color[i][j][k][g] 则我们可以直接跳过中间步骤,使得小人移动到k g上,根据color的定义,这样做是显然合法的,而若color[i][j][x][y]不等于color[i][j][k][g],等价于从当前分支无论如何也找到不一直方式使得小人移动到k g上,截断。

  没啥好说的,毒瘤题,纯纯恶心人。

  block[i][j][c]表示满足color[i][j][k][g]=ck g的数量。

  visited[i][j][c]c=0表示i j是否被取为终点过,其他则是箱子在i j上,人在箱子的第c个方向上的分支,是否被搜索过。

  XQ YQ ZQ head tail 就是维护队列的变量。

  就这样,仁至义尽。

import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

public class Main {
	
	public static void main(String[] args) { new Main().run(); }
	
	final int[][] offset = {null, {-1, 0}, {0, 1}, {1, 0}, {0, -1}};
	
	final int N = 10, M = 10, K = 5;
	
	int n, m, x, y, z, ans, head, tail;
	
	int[][][] block = new int[N][M][K];
	
	int[][][][] color = new int[N][M][N][M];
	
	boolean[][][] visited = new boolean[N][M][K];
	
	boolean[][] wall = new boolean[N][M];
	
	int[] XQ = new int[10 * N * M];
	int[] YQ = new int[10 * N * M];
	int[] ZQ = new int[10 * N * M];
	
	void run() {
		n = nextInt();
		m = nextInt();
		for (int i = 0; i < n; ++i)
			for (int j = 0; j < m; ++j)
				if (nextInt() == 1) wall[i][j] = true;
		for (int i = 0; i < n; ++i)
			for (int j = 0; j < m; ++j) {
				if (wall[i][j]) continue;
				wall[i][j] = true;
				for (int c = 1; c < K; ++c) {
					XQ[0] = i + offset[c][0];
					YQ[0] = j + offset[c][1];
					head = 0; tail = 1;
					while (head < tail) {
						x = XQ[head]; y = YQ[head]; ++head;
						if (indexOutOfBounds(x, y) || color[i][j][x][y] > 0) continue;
						color[i][j][x][y] = c; ++block[i][j][c];
						for (int k = 1; k < K; ++k) {
							XQ[tail] = x + offset[k][0];
							YQ[tail] = y + offset[k][1]; ++tail;
						}
					}
				}
				wall[i][j] = false;
			}
		for (int i = 0; i < n; ++i)
			for (int j = 0; j < m; ++j)
				for (int c = 1; c < K; ++c) {
					if (block[i][j][c] == 0) continue;
					int end = 0, endOnBlock = 0;
					XQ[0] = i; YQ[0] = j; ZQ[0] = c;
					head = 0; tail = 1;
					clearVisited();
					visited[i][j][0] = true;
					while (head < tail) {
						x = XQ[head]; y = YQ[head]; z = ZQ[head]; ++head;
						if (indexOutOfBounds(x, y) || visited[x][y][z]) continue;
						visited[x][y][z] = true;
						for (int d = 1; d < K; ++d)
							if (!indexOutOfBounds(x + offset[d][0], y + offset[d][1]) &&
								color[x][y][x + offset[d][0]][y + offset[d][1]] == color[x][y][x + offset[z][0]][y + offset[z][1]]) {
								XQ[tail] = x; YQ[tail] = y; ZQ[tail] = d;
								++tail;
							}
						if (indexOutOfBounds(x + 2 * offset[z][0], y + 2 * offset[z][1])) continue;
						x += offset[z][0]; y += offset[z][1];
						if (!visited[x][y][0]) {
							if (color[i][j][x][y] == c) ++endOnBlock;
							visited[x][y][0] = true;
							++end;
						}
						XQ[tail] = x; YQ[tail] = y; ZQ[tail] = z; ++tail;
					}
					ans += block[i][j][c] * end - endOnBlock;
				}
		System.out.println(ans);
	}

	void clearVisited() {
		for (int i = 0; i < n; ++i)
			for (int j = 0; j < m; ++j)
				for (int k = 0; k < K; ++k) visited[i][j][k] = false;
	}
	
	boolean indexOutOfBounds(int x, int y) { return x < 0 || x >= n || y < 0 || y >= m || wall[x][y]; }
	
	StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
	
	int nextInt() {
		try {
			in.nextToken();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (int)in.nval;
	}
}
  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值