第 26 场 蓝桥月赛

101 阅读10分钟

www.lanqiao.cn/oj-contest/…

前言

第 26 场蓝桥月赛是蓝桥杯系列赛事中的一场重要比赛,于 2025 年 2 月 22 日举行,采用 ACM 赛制,赛题共计 6 题,比赛时间为 2 小时。这场比赛吸引了众多编程爱好者和算法学习者参与,旨在通过一系列具有挑战性的算法问题,考察参赛者的编程能力、算法思维以及解决问题的能力。赛后练习功能为参赛者提供了继续提升的机会,尽管不增加个人得分,但仍然是巩固知识和技能的宝贵资源。本文将对比赛中的题目进行考点概括和分析,帮助读者更好地理解比赛内容和解题思路。

考点概括

1. 好汤圆

  • 考点:签到题,基础数学运算(除法)。
  • 解题思路:题目要求计算让所有汤圆浮起来所需的时间,通过简单的除法运算即可得出答案。

2. 灯笼猜谜

  • 考点:区间移动,左右距离计算。
  • 解题思路:通过模拟移动过程,计算从一个区间到另一个区间的距离,需要注意当前位置与区间边界的相对位置关系。

3. 元宵分配

  • 考点:模拟,数组操作。
  • 解题思路:对数组进行排序后,通过遍历数组的前半部分计算总和,考察了数组的基本操作和排序算法的使用。

4. 摆放汤圆

  • 考点:动态规划,模运算,预处理。
  • 解题思路:通过动态规划解决皇后对称放置问题,定义状态转移方程,并利用模运算处理大数问题,同时通过预处理提高效率。

5. 元宵交友

  • 考点:二分查找。
  • 解题思路:利用二分查找优化查找过程,通过遍历所有可能的时间间隔,计算可以邀请的朋友数量,考察了二分查找的应用和循环控制。

6. 灯笼大乱斗

  • 考点:数组操作,分段处理,组合数学。
  • 解题思路:通过计算两个新数组(C 和 D),找到满足条件的连续区间,并利用组合数学公式计算区间对的数量,考察了数组操作、分段处理和组合数学的应用。

好汤圆 签到题 除法

题干:

已知锅里有 2025 个汤圆。肉丸子跳进去后,每分钟都会产生足够的热量使 15 个汤圆浮起来。

请问,肉丸子需要多少分钟才能让所有汤圆都浮起来?

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        //在此输入您的代码...
        int n=2025/15;
        System.out.println(n);
        scan.close();
    }
}

灯笼猜谜 区间移动 左右距离

import java.util.Scanner;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int N = scan.nextInt(); // 区间对的数量
        int M = scan.nextInt(); // 未使用的变量
        int[][] arr = new int[N][2]; // 存储每个区间的左右边界
        for (int i = 0; i < N; i++) {
            arr[i][0] = scan.nextInt(); // 区间左边界
            arr[i][1] = scan.nextInt(); // 区间右边界
        }
        long index = 1, total = 0; // 初始位置为1,总移动距离为0
        for (int i = 0; i < arr.length; i++) {
            int left = arr[i][0]; // 当前区间的左边界
            int right = arr[i][1]; // 当前区间的右边界
            if (index < left) {
                // 如果当前位置在当前区间的左边,移动到左边界
                total += Math.abs(index - left); // 计算移动距离
                index = left; // 更新当前位置为左边界
            } else if (index > right) {
                // 如果当前位置在当前区间的右边,移动到右边界
                total += Math.abs(index - right); // 计算移动距离
                index = right; // 更新当前位置为右边界
            }
        }
        System.out.println(total); // 输出总移动距离
    }
}

元宵分配 模拟

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt(); // 读取数组的长度
        int K = scanner.nextInt(); // 读取一个未使用的变量
        int[] arr = new int[N]; // 创建一个长度为N的数组
        for (int i = 0; i < N; i++) {
            arr[i] = scanner.nextInt(); // 读取数组的每个元素
        }
        Arrays.sort(arr); // 对数组进行升序排序

        // 使用long来存储,避免整数溢出
        long sum = 0; // 初始化总和为0
        for (int i = 0; i < N / 2; i++) { // 遍历数组的前半部分
            sum += arr[i]; // 累加前半部分的元素
        }
        System.out.println(sum); // 输出总和
    }
}

摆放汤圆 对称 n 皇后

问题描述:

  • 皇后问题:在一个 ( k *k ) 的棋盘上放置 ( k ) 个皇后,使得它们互不攻击。皇后可以攻击同一行、同一列以及同一对角线上的其他皇后。
  • 对称放置:皇后关于主对角线对称放置,即如果第 ( i ) 行第 ( j ) 列放置了一个皇后,则第 ( j ) 行第 ( i ) 列也必须放置一个皇后。

解题思路:

  1. 动态规划
    • 使用动态规划(DP)来解决这个问题。
    • 定义 dp[k] 表示在 ( k \times k ) 的棋盘上对称放置皇后的方法数。
    • 初始条件:
      • dp[0] = 1:空矩阵有一种方法(即不放置任何皇后)。
      • dp[1] = 1:1x1 的矩阵有一种方法(放置一个皇后)。
    • 状态转移方程:
      • 对于 ( k \times k ) 的棋盘,有两种情况:
        1. 不放置皇后:方法数为 dp[k-1]
        2. 放置皇后:选择一个位置放置皇后,皇后必须关于主对角线对称。因此,可以选择 ( k-1 ) 个位置放置皇后,每个位置的放置方法数为 dp[k-2]
      • 因此,状态转移方程为:
        [
        dp[k] = dp[k-1] + (k-1) \times dp[k-2]
        ]
  1. 模运算
    • 由于结果可能非常大,需要对结果取模(模数为 ( 10^9 + 7 ))。
  1. 预处理
    • 在程序开始时,预先计算并存储所有可能的 ( k ) 值的 dp[k],这样在处理多个查询时可以直接输出结果,提高效率。

代码详细注释:

import java.util.Scanner;

public class Main {
    static long MOD = (long)(1e9 + 7); // 定义模数
    static int N = (int)(1e6 + 5); // 定义最大值
    static long[] dp = new long[N]; // 定义动态规划数组

    static void solve() {
        // dp[k] 表示 k x k 矩阵的对称放置方法数

        // 初始条件
        dp[0] = 1; // 空矩阵有一种方法
        dp[1] = 1; // 1x1 矩阵有一种方法

        // 填充 dp 数组
        for (int k = 2; k < N; k++) {
            // 状态转移方程
            dp[k] = (dp[k - 1] + (k - 1) * dp[k - 2]) % MOD;
        }
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int t = scan.nextInt(); // 读取测试用例的数量
        solve(); // 预处理所有可能的 dp 值
        while (t-- > 0) { // 处理每个测试用例
            int n = scan.nextInt(); // 读取棋盘大小
            System.out.println(dp[n]); // 输出结果
        }
        scan.close(); // 关闭 Scanner
    }
}

示例运行:

假设输入:

2
3
4
  • 处理过程:
    • 预处理 dp 数组:
      • dp[0] = 1
      • dp[1] = 1
      • dp[2] = (dp[1] + 1 * dp[0]) % MOD = (1 + 1 * 1) % MOD = 2
      • dp[3] = (dp[2] + 2 * dp[1]) % MOD = (2 + 2 * 1) % MOD = 4
      • dp[4] = (dp[3] + 3 * dp[2]) % MOD = (4 + 3 * 2) % MOD = 10
    • 输出结果:
      • 对于 ( 3 \times 3 ) 的棋盘,方法数为 4
      • 对于 ( 4 \times 4 ) 的棋盘,方法数为 10

输出:

4
10

考点分析:

  1. 动态规划
    • 状态定义dp[k] 表示 ( k \times k ) 矩阵的对称放置方法数。
    • 状态转移方程dp[k] = dp[k-1] + (k-1) * dp[k-2]
    • 初始条件dp[0] = 1dp[1] = 1
  1. 模运算
    • 由于结果可能非常大,需要对结果取模(模数为 ( 10^9 + 7 ))。
  1. 预处理
    • 在程序开始时,预先计算并存储所有可能的 ( k ) 值的 dp[k],这样在处理多个查询时可以直接输出结果,提高效率。
  1. 循环和条件语句
    • 使用循环和条件语句处理多个测试用例。

元宵交友 二分查找

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt(); // 朋友的数量
        int[] friends = new int[n]; // 存储每个朋友的到达时间

        for (int i = 0; i < n; i++) {
            friends[i] = scan.nextInt(); // 读取每个朋友的到达时间
        }
        Arrays.sort(friends); // 对到达时间进行升序排序

        // 对于每个可能的最小时间间隔 k(从 1 到 n)
        for (int k = 1; k <= n; k++) {
            int cnt = 0; // 计数器,记录可以邀请的朋友数量
            int i = 0; // 当前考虑的朋友索引
            while (true) {
                cnt++; // 当前朋友可以邀请
                // 使用二分查找找到下一个可以邀请的朋友
                int index = binarySearch(friends, i + 1, n - 1, friends[i] + k);
                if (index >= n) {
                    break; // 如果没有找到,结束循环
                }
                i = index; // 更新当前考虑的朋友索引
            }
            System.out.print(cnt + " "); // 输出当前 k 下可以邀请的朋友数量
        }

        scan.close(); // 关闭 Scanner
    }

    // 二分查找
    public static int binarySearch(int[] arr, int left, int right, int target) {
        int l = left;
        int r = right;
        while (l <= r) {
            int mid = l + ((r - l) >> 1); // 计算中间位置
            if (arr[mid] >= target) {
                r = mid - 1; // 如果中间值大于等于目标值,向左半部分查找
            } else {
                l = mid + 1; // 否则,向右半部分查找
            }
        }
        return l; // 返回目标值应该插入的位置
    }
}

灯笼大乱斗 区间重叠 分段处理

问题描述:

  • 输入
    • 一个整数 (n),表示数据的长度。
    • 两组数据 (A) 和 (B),每组包含 (n) 个整数。
  • 输出
    • 输出满足条件的区间对的数量。

条件说明:

  • 对于每个位置 (i),定义两个新的数组 (C) 和 (D):
    • (C[i] = A[i] - B[i])
    • (D[i] = A[i] + B[i])
  • 一个区间 ([i, j]) 满足条件,当且仅当对于所有 (k)((i \leq k \leq j)),满足 (C[k] \leq C[k+1]) 和 (D[k] \leq D[k+1])。

解题思路:

  1. 预处理
    • 计算两个数组 (C) 和 (D),分别表示每个位置的 (A[i] - B[i]) 和 (A[i] + B[i])。
  1. 分段处理
    • 遍历数组 (C) 和 (D),找到满足条件的连续区间。
    • 使用一个列表 segments 来存储每个连续区间的长度。
  1. 计算结果
    • 对于每个长度为 (k) 的连续区间,满足条件的区间对数量为 (\frac{k \times (k - 1)}{2})。
    • 累加所有区间的区间对数量,得到最终结果。

代码详细注释:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine()); // 读取数据长度
        if (n < 2) {
            System.out.println(0); // 如果长度小于2,直接输出0
            return;
        }

        long[] A = new long[n]; // 存储第一组数据
        long[] B = new long[n]; // 存储第二组数据
        
        // 读取第一组数据
        String[] aStrs = br.readLine().split(" ");
        // 读取第二组数据
        String[] bStrs = br.readLine().split(" ");
        
        // 将字符串数组转换为长整型数组
        for (int i = 0; i < n; i++) {
            A[i] = Long.parseLong(aStrs[i]);
            B[i] = Long.parseLong(bStrs[i]);
        }

        // 计算C和D数组
        long[] C = new long[n];
        long[] D = new long[n];
        for (int i = 0; i < n; i++) {
            C[i] = A[i] - B[i]; // C[i] = A[i] - B[i]
            D[i] = A[i] + B[i]; // D[i] = A[i] + B[i]
        }

        // 分段处理
        List<Integer> segments = new ArrayList<>();
        int currentLength = 1; // 当前连续区间的长度
        for (int i = 1; i < n; i++) {
            // 如果当前区间满足条件,增加当前区间的长度
            if (C[i] > C[i - 1] && D[i] > D[i - 1]) {
                currentLength++;
            } else {
                // 否则,将当前区间的长度添加到列表中,并重置当前区间的长度
                segments.add(currentLength);
                currentLength = 1;
            }
        }
        // 添加最后一个区间的长度
        segments.add(currentLength);

        // 计算结果
        long total = 0;
        for (int k : segments) {
            if (k >= 2) {
                // 对于长度为k的区间,满足条件的区间对数量为k * (k - 1) / 2
                total += (long) k * (k - 1) / 2;
            }
        }
        System.out.println(total); // 输出最终结果
    }
}

示例运行:

假设输入:

5
1 2 3 4 5
1 1 1 1 1
  • 处理过程:
    • (A = [1, 2, 3, 4, 5])
    • (B = [1, 1, 1, 1, 1])
    • (C = [0, 1, 2, 3, 4])
    • (D = [2, 3, 4, 5, 6])
    • 遍历 (C) 和 (D),发现整个数组都满足条件,因此只有一个长度为 5 的连续区间。
    • 计算结果:(\frac{5 \times (5 - 1)}{2} = 10)

输出:

10

考点分析:

  1. 数组操作
    • 读取和处理两组数据。
    • 计算两个新的数组 (C) 和 (D)。
  1. 分段处理
    • 使用一个列表 segments 来存储每个连续区间的长度。
    • 遍历数组,找到满足条件的连续区间。
  1. 组合数学
    • 对于长度为 (k) 的连续区间,满足条件的区间对数量为 (\frac{k \times (k - 1)}{2})。
  1. 输入输出
    • 使用 BufferedReader 读取输入,提高效率。
    • 输出最终结果。

注意事项:

  • 边界条件:确保处理 (n < 2) 的情况。
  • 数组索引:在遍历时,确保索引不会越界。
  • 性能优化:通过分段处理,避免暴力搜索,提高程序的效率。