第十二届蓝桥杯 2021年国赛真题 (Java 大学A组)

本文介绍了编程竞赛中常见的算法问题,包括质数判断、完全日期计算、树的最小权值、覆盖问题的变种以及二进制序列的处理。文章通过实例展示了如何运用朴素算法、动态规划、前缀和、二进制操作等方法解决这些问题,并提供了相应的代码实现,展现了算法在解决实际问题中的应用和优化思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


  二十九号考马原,等到二十八号再背,

  更新中。。。


#A 纯质数

本题总分:5 分


问题描述

  如果一个正整数只有 1 1 1 和它本身两个约数,则称为一个质数(又称素数)。
  前几个质数是: 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 , ⋅ ⋅ ⋅ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, · · · 2,3,5,7,11,13,17,19,23,29,31,37,
  如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如: 2 , 3 , 5 , 7 , 23 , 37 2, 3, 5, 7, 23, 37 2,3,5,7,23,37 都是纯质数,而 11 , 13 , 17 , 19 , 29 , 31 11, 13, 17, 19, 29, 31 11,13,17,19,29,31 不是纯质数。当然 1 , 4 , 35 1, 4, 35 1,4,35 也不是纯质数。
  请问,在 1 1 1 20210605 20210605 20210605 中,有多少个纯质数?


答案提交

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


1903


按序枚举


  一种朴素的想法,在打表 [ 1 , 20210605 ] [1,20210605] [1,20210605] 间的质数时,额外的进行一次纯质数效验,并将结果累加起来。

import java.util.ArrayList;
import java.util.List;

public class Test {
   

    public static final int N = 20210605;

    public static void main(String[] args) {
   
        boolean[] marked = new boolean[N + 1];
        List<Integer> primes = new ArrayList();
        marked[0] = marked[1] = true;
        int ans = 0;
        for (int i = 2; i <= N; i++) {
   
            if (!marked[i]) {
   
                primes.add(i);
                boolean flag = false;
                for (int k = i; k > 0; k /= 10)
                    if (flag = marked[k % 10]) break;
                if (!flag) ans++;
            }
            for (int p : primes) {
   
                if (p * i > N)  break;
                marked[p * i] = true;
                if (i % p == 0) break;
            }
        }
        System.out.println(ans);
    }
}


按位枚举


  在朴素的解法中,我们会发现,很多拆分判断一个质数是否是纯质数是没有必要的,因为在 [ 0 , 10 ) [0,10) [0,10) 中,质数仅占 { 2 , 3 , 5 , 7 } \{2,3,5,7\} { 2,3,5,7} 四位,如果仅判断这四个数字组合成的数是否是质数的话,性能能否有进一步的提升呢?

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Test {
   

    public static final int N = 20210605;

    public static int[][] digits = new int[8][4];

    public static void main(String[] args) {
   
        boolean[] primes = new boolean[N + 1];
        List<Integer> helper = new ArrayList();
        Arrays.fill(primes, 2, N, true);
        int ans = 0;
        for (int i = 2; i <= N; i++) {
   
            if (primes[i]) helper.add(i);
            for (int p : helper) {
   
                if (p * i > N)  break;
                primes[p * i] = false;
                if (i % p == 0) break;
            }
        }
        digits[0] = new int[]{
   2, 3, 5, 7};
        for (int k = 1; k < 7; k++)
            for (int i = 0; i < 4; i++)
                digits[k][i] = digits[k - 1][i] * 10;
        digits[7] = new int[]{
    digits[6][0] * 10 };
        System.out.println(dfs(primes, 0, 0));
    }

    public static int dfs(boolean[] primes, int k, int depth) {
   
        if (depth == 8) return k <= N && primes[k] ? 1 : 0;
        int ans = primes[k] ? 1 : 0;
        for (int a : digits[depth])
            ans += dfs(primes, k + a, depth + 1);
        return ans;
    }
}

  实际上性能并没有进一步的提升,并且代码量还有所增加。

  这是因为在 ( 1 , N ] (1,N] (1,N] 这个范围中的质数大约有 N ln ⁡ N \cfrac{N}{\ln N} lnNN 个,而按位组成的可能是纯质数的数大约有 4 ln ⁡ N 4^{\ln N} 4lnN 个,这显然不是一个增值量级的。


#B 完全日期

本题总分:5 分


问题描述

  如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日期。
  例如: 2021 2021 2021 6 6 6 5 5 5 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 5 = 16 2 + 0 + 2 + 1 + 6 + 5 = 16 2+0+2+1+6+5=16,而 16 16 16 是一个完全平方数,它是 4 4 4 的平方。所以 2021 2021 2021 6 6 6 5 5 5 日是一个完全日期。
  例如: 2021 2021 2021 6 6 6 23 23 23 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16 2+0+2+1+6+2+3=16,是一个完全平方数。所以 2021 2021 2021 6 6 6 23 23 23 日也是一个完全日期。
  请问,从 2001 2001 2001 1 1 1 1 1 1 日到 2021 2021 2021 12 12 12 31 31 31 日中,一共有多少个完全日期?


答案提交

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


977


Java党的完全胜利


  LocalDate好用。


朴素解法


  枚举 [ 2001 [2001 [2001- 01 01 01- 01 01 01 2021 2021 2021- 12 12 12- 31 ] 31] 31] 这个区间间内的时间,判断然后累加。

import java.time.LocalDate;

public class Test {
   

    public static final int maxPerfect = 2 + 0 + 1 + 9 + 0 + 9 + 2 + 9;

    public static final boolean[] perfect = new boolean[maxPerfect + 1];

    public static LocalDate start = LocalDate.of(2001, 01, 01);

    public static LocalDate end = LocalDate.of(2021, 12, 31);

    public static void main(String[] args) {
   
        int count = 0;
        for (int i = 1; i * i<= maxPerfect; i++)
            perfect[i * i] = true;
        while (end.compareTo(start) >= 0) {
   
            if (perfect[calc(start)])
                count++;
            start = start.plusDays(1);
        }
        System.out.println(count);
    }

    public static int calc(LocalDate date) {
   
        String dateStr = date.toString();
        int res = 0;
        for (int i = dateStr.length() - 1; i >= 0; i--)
            if (Character.isDigit(dateStr.charAt(i)))
                res += Character.digit(dateStr.charAt(i), 10);
        return res;
    }
}

朴素改进


  在 [ 2001 [2001 [2001- 01 01 01- 01 01 01 2021 2021 2021- 12 12 12- 31 ] 31] 31] 中,各数位之和不仅存在最大值 2 + 0 + 1 + 9 + 0 + 9 + 2 + 9 = 32 2 + 0 + 1 + 9 + 0 + 9 + 2 + 9 = 32 2+0+1+9+0+9+2+9=32,还存在有最小值 2 + 0 + 0 + 1 + 0 + 1 + 0 + 1 2 + 0 + 0 + 1 + 0 + 1 + 0 + 1 2+0+0+1+0+1+0+1,也就是说,可能的完全平方数,不外乎 3 × 3 3 × 3 3×3 4 × 4 4 × 4 4×4 5 × 5 5 × 5 5×5 三个,就这一点我们来简化我们的代码。

import java.time.LocalDate;

public class Test {
   

    public static LocalDate start = LocalDate.of(2001, 01, 01);

    public static LocalDate end = LocalDate.of(2021, 12, 31);

    public static void main(String[] args) {
   
        int count = 0;
        while (end.compareTo(start) >= 0) {
   
            if (isPerfect(start)) count++;
            start = start.plusDays(1);
        }
        System.out.println(count);
    }

    public static boolean isPerfect(LocalDate date) {
   
        String dateStr = date.toString();
        int sum = 0;
        for (int i = dateStr.length() - 1; i >= 0; i--)
            if (Character.isDigit(dateStr.charAt(i)))
                sum += Character.digit(dateStr.charAt(i), 10);
        return sum == 3 * 3 || sum == 4 * 4 || sum == 5 * 5;
    }
}

不依赖 API 的实现


  先统计出平年月份和日期各数位之和的出现次数,然后遍历年份时额外的判断一道是否为闰年。

public class Test {
   

    public static void main(String[] args) {
   
        int[] bigMonth = {
    1, 3, 5, 7, 8, 1 + 0, 1 + 2 };
        int[] smallMonth = {
    4, 6, 9, 1 + 1 };
        int[] calendar = new int[9 + 2 + 9 + 1];
        int ans = 0;
        for (int day = 1; day <= 31; day++)
            for (int month : bigMonth)
                calendar[month + calc(day)]++;
        for (int day = 1; day <= 30; day++)
            for (int month : smallMonth)
                calendar[month + calc(day)]++;
        for (int day = 1; day <= 28; day++)
            calendar[2 + calc(day)]++;
        for (int year = 2001; year <= 2021; year++) {
   
            if (isLeapYear(year))
                if (isLeapYear(year + 2 + 2 + 9)) ans++;
            for (int i = 0; i < calendar.length; i++) {
   
                if (calendar[i] == 0) continue;
                if (isPerfect(calc(year) + i))
                    ans += calendar[i];
            }
        }
        System.out.println(ans);
    }

    public static int calc(int n) {
   
        int res = 0;
        do
            res += n % 10;
        while ((n /= 10) > 0);
        return res;
    }

    public static boolean isPerfect(int num) {
    return num == 3 * 3 || num == 4 * 4 || num == 5 * 5; }

    public static boolean isLeapYear(int year) {
    return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0; }
}

#C 最小权值

本题总分:10 分


问题描述

  对于一棵有根二叉树 T T T,小蓝定义这棵树中结点的权值 W ( T ) W(T) W(T) 如下:
  空子树的权值为 0 0 0
  如果一个结点 v v v 有左子树 L L L, 右子树 R R R,分别有 C ( L ) C(L) C(L) C ( R ) C(R) C(R) 个结点,则 W ( v ) = 1 + 2 W ( L ) + 3 W ( R ) + ( C ( L ) ) 2 C ( R ) W(v) = 1 + 2W(L) + 3W(R) + (C(L))^{2} C(R) W(v)=1+2W(L)+3W(R)+(C(L))2C(R)
  树的权值定义为树的根结点的权值。
  小蓝想知道,对于一棵有 2021 2021 2021 个结点的二叉树,树的权值最小可能是多少?


答案提交

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


2653631372


动态规划


  根据题意,显然有 N = 2021 N = 2021 N=2021 W ( 0 ) = 0 W(0) = 0 W(0)=0 W ( N ) = min ⁡ { 1 + 2 W ( L ) + 3 W ( R ) + ( C ( L ) ) 2 C ( R ) } W(N) = \min \{1 + 2W(L) + 3W(R) + (C(L))^{2}C(R)\} W(N)=min{ 1+2W(L)+3W(R)+(C(L))2C(R)},其中 L + R = N − 1 L + R = N - 1 L+R=N1 0 ≤ L ≤ R ≤ N 0 \leq L \leq R \leq N 0LRN

  状态转移方程就在脸上: d p ( y ) = { 0 i = 0 min ⁡ { 1 + 2 d p ( l ) + 3 d p ( r ) + l 2 r } l + r = i − 1 , 0 ≤ l ≤ r ≤ i dp(y)=\left\{ \begin{array}{lr} 0&i = 0\\ \min \{1 + 2dp(l) + 3dp(r) + l^{2}r\}&l + r = i - 1,0 \leq l \leq r \leq i \end{array} \right. dp(y)={ 0min{ 1+2dp(l)+3dp(r)+l2r}i=0l+r=i10lri  也是道签到题。

public class Test {
   

    public static final int N = 2021;

    public static void main(String[] args) {
   
        long[] dp = new long[N + 1];
        for (int i = 1; i <= N; i++) {
   
            dp[i] = Long.MAX_VALUE;
            for (int l = i >> 1; l >= 0; l--)
                dp[i] = Math.min(dp[i],
                    1 + 2 * dp[l] + 3 * dp[i - l - 1] + l * l * (i - l - 1));
        }
        System.out.println(dp[N]);
    }
}

#D 覆盖

本题总分:10 分


问题描述

  小蓝有一个国际象棋的棋盘,棋盘的大小为 8 × 8 8 × 8 8×8,即由 8 8 8 8 8 8 列共 64 64 64 个方格组成。棋盘上有美丽的图案,因此棋盘旋转后与原来的棋盘不一样。
  小蓝有很多相同的纸片,每张纸片正好能覆盖棋盘的两个相邻方格。小蓝想用 32 32 32 张纸片正好将棋盘完全覆盖,每张纸片都覆盖其中的两个方格。
  小蓝发现,有很多种方案可以实现这样的覆盖。如果棋盘比较小,方案数相对容易计算,比如当棋盘是 2 × 2 2 × 2 2×2 时有两种方案,当棋盘是 4 × 4 4 × 4 4×4 时有 36 36 36 种方案。但是小蓝算不出他自己的这个 8 × 8 8 × 8 8×8 的棋盘有多少种覆盖方案。
  请帮小蓝算出对于这个 8 × 8 8 × 8 8×8 的棋盘总共有多少种覆盖方案。


答案提交

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


12988816


变种八皇后


  不知道怎么分类,

  但小规模的完全摆放问题都可以照八皇后的板子做。

public class Test {
   

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

    int N = 8, ans = 0;

    boolean[][] marked = new boolean[N][N];

    void run() {
   
        dfs(0, 0);
        System.out.println(ans);
    }

    void dfs(int x, int y) {
   
        if (x == N) ans++;
        else if (marked[x][y]) {
   
            if (y == N - 1) dfs(x + 1, 0);
            else dfs(x, y + 1);
        } else {
   
            marked[x][y] = true;
            if (y + 1< N && !marked[x][y + 1]) {
   
                marked[x][y + 1] = true;
                dfs(x, y + 1);
                marked[x][y + 1] = false;
            }
            if (x + 1< N && !marked[x + 1][y]) {
   
                marked[x + 1][y] = true;
                dfs(x, y);
                marked[x + 1][y] = false;
            }
            marked[x][y] = false;
        }
    }
}

  稍微说明一下第 28 28 28 行,

  其实是复用了第 17 17 17 行的这个 i f \mathrm{if} if

  偷个小懒。


状压 DP


  自然地去思考用一串二进制描述一行,

  大致就是 0   /   1 0\:/\ 1 0/ 1 表示该位置为 横 / / / 竖 覆盖纸片的一半,

  容易知道, 0 0 0 必须呈偶数个连续,即横覆盖纸片必须完整,

  上一行的 1 1 1 与下一行的 1 1 1 必须一一对应,即相邻两行必须满足 l i n e [ i − 1 ]   &   l i n e [ i ] = l i n e [ i − 1 ] \mathrm{line[i - 1]\ \&\ line[i] = line[i - 1]} line[i1] & line[i]=line[i1]

  转移后的行状态改为 l i n e [ i ] − l i n e [ i − 1 ] \mathrm{line[i] - line[i - 1]} line[i]line[i1],即取出不完整的竖覆盖纸片。

  整个算法复杂度在 O ( N 4 N ) O(N4^{N}) O(N4N)

  当然我称它为 “自然” 是有原因的,

  因为还有总较为反常的思路,当然你也可以用上述状压优化的方式去思考它。

  具体地说:

  一个方格必然会被四种纸片覆盖:竖摆上、竖摆下、横摆左、横摆右。

  对于每一行,我们使用 1 1 1 表示横着摆放的纸片的上半部分, 0 0 0 则表示其他三种情况。

  容易知道,每次转移必须满足

   l i n e [ i − 1 ]   &   l i n e [ i ] = 0 \mathrm{line[i - 1]\ \&\ line[i] = 0} line[i1] & line[i]=0

   l i n e [ i − 1 ]   ∣   l i n e [ i ] \mathrm{line[i - 1]\ \mid\ line[i]} line[i1]  line[i] 0 0 0 呈偶数个连续。

  使用这种思路可以少做一次减法,

  略微优化了一点点。

public class Test {
   

    static int N = 8, M;

    public static void main(String[] args) {
   
        int[][] dp = new int[2][M = 1 << N];
        boolean[] check = new boolean[M];
        boolean flag, even;
        for (int i = 0; i < M; i++) {
   
            flag = even = true;
            for (int j = 0; j < N; j++)
                if ((i >> j & 1) == 0) even = !even;
                else {
   
                    flag &= even;
                    even = true;
                }
            check[i] = flag & even;
        }
        dp[0][0] = 1;
        for (int i = 1; i <= N; i++)
            for (int j = 0; j < M; j++) {
   
                dp[i & 1][j] = 0;
                for (int k = 0; k < M; k++)
                    if ((j & k) == 0 && check[j | k])
                        dp[i & 1][j] += dp[(i - 1) & 1][k];
            }
        System.out.println(dp[N & 1][0]);
    }
}

#E 123

时间限制: 5.0 5.0 5.0s 内存限制: 512.0 512.0 512.0MB 本题总分: 15 15 15


问题描述

  小蓝发现了一个有趣的数列,这个数列的前几项如下:
   1 , 1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , 4 , . . . 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, ... 1,1,2,1,2,3,1,2,3,4,...
  小蓝发现,这个数列前 1 1 1 项是整数 1 1 1,接下来 2 2 2 项是整数 1 1 1 2 2 2,接下来 3 3 3 项是整数 1 1 1 3 3 3,接下来 4 4 4 项是整数 1 1 1 4 4 4,依次类推。
  小蓝想知道,这个数列中,连续一段的和是多少。


输入格式

  输入的第一行包含一个整数 T T T,表示询问的个数。
  接下来 T T T 行,每行包含一组询问,其中第 i i i 行包含两个整数 l i l_{i} li r i r_{i} ri,表示询问数列中第 l i l_{i} li 个数到第 r i r_{i} ri 个数的和。


输出格式

  输出 T T T 行,每行包含一个整数表示对应询问的答案。


测试样例1

Input:
3
1 1
1 3
5 8

Output:
1
4
8

评测用例规模与约定

  对于 10 10 10% 的评测用例, 1 ≤ T ≤ 30 , 1 ≤ l i ≤ r i ≤ 100 1 \leq T \leq 30, 1 \leq l_{i} \leq r_{i} ≤ 100 1T30,1liri100
  对于 20 20 20% 的评测用例, 1 ≤ T ≤ 100 , 1 ≤ l i ≤ r i ≤ 1000 1 \leq T \leq 100, 1 \leq l_{i} \leq r_{i} ≤ 1000 1T100,1liri1000
  对于 40 40 40% 的评测用例, 1 ≤ T ≤ 1000 , 1 ≤ l i ≤ r i ≤ 1 0 6 1 \leq T \leq 1000, 1 \leq l_{i} \leq r_{i} ≤ 10^{6} 1T1000,1l

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值