Csp J 2025 题目分析

【25CSPJ普及组】拼数

这是一个字符串处理问题,需要从给定的字符串中提取所有数字字符,然后组成最大的正整数。

思路:

  1. 从字符串中提取所有的数字字符

  2. 对数字字符进行排序(降序),这样最大的数字会在前面

  3. 注意:不能有前导零,所以如果第一个字符是'0',需要特殊处理

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    string s;
    cin >> s;

    // 提取所有数字字符
    vector<char> digits;
    for (char c : s) {
        if (isdigit(c)) {
            digits.push_back(c);
        }
    }

    // 如果没有数字,返回空(但题目保证至少有一个1-9的数字)
    if (digits.empty()) {
        cout << "" << endl;
        return 0;
    }

    // 对数字进行降序排序
    sort(digits.rbegin(), digits.rend());

    // 如果最大的数字是0,说明所有数字都是0,返回单个0
    // 但题目保证至少有一个1-9的数字,所以这种情况不会出现
    if (digits[0] == '0') {
        cout << "0" << endl;
        return 0;
    }

    // 直接输出排序后的数字字符串
    string result(digits.begin(), digits.end());
    cout << result << endl;

    return 0;
}

代码解释:

  1. 提取数字:遍历字符串,使用isdigit()函数识别数字字符,将它们存入vector中。

  2. 排序数字:使用sort(digits.rbegin(), digits.rend())对数字进行降序排序,这样最大的数字会排在前面。

  3. 特殊情况处理

  4. 如果没有数字(题目保证不会出现)
  5. 如果所有数字都是0(题目保证不会出现,因为至少有一个1-9的数字)

  6. 输出结果:将排序后的数字字符连接成字符串输出。

示例验证: - 输入:"1a01b" - 提取的数字:['1', '0', '1'] - 排序后:['1', '1', '0'] - 输出:"110"

这个算法的时间复杂度是O(n log n),其中n是字符串长度,主要来自排序操作。空间复杂度是O(n),用于存储提取的数字字符。

【25CSPJ普及组】座位

根据题目描述,我们需要按照蛇形分配座位的方式,根据成绩从高到低分配座位,然后找出小R的座位位置。

思路: 1. 首先将所有成绩从高到低排序,确定每个成绩的排名 2. 根据蛇形排列的规则,计算每个排名对应的座位位置 3. 找到小R成绩对应的排名,输出其座位位置

蛇形排列规则: - 奇数列:从上到下分配(第1行到第n行) - 偶数列:从下到上分配(第n行到第1行)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int total = n * m;
    vector<int> scores(total);

    // 读入所有成绩
    for (int i = 0; i < total; i++) {
        cin >> scores[i];
    }

    // 小R的成绩是第一个成绩
    int r_score = scores[0];

    // 创建成绩副本并排序(从高到低)
    vector<int> sorted_scores = scores;
    sort(sorted_scores.begin(), sorted_scores.end(), greater<int>());

    // 找到小R成绩在排序后的位置(排名,从1开始)
    int rank = 0;
    for (int i = 0; i < total; i++) {
        if (sorted_scores[i] == r_score) {
            rank = i + 1;
            break;
        }
    }

    // 根据排名计算座位位置
    int col = (rank - 1) / n + 1;  // 列号(从1开始)
    int pos_in_col = (rank - 1) % n;  // 在列中的位置(从0开始)

    int row;
    if (col % 2 == 1) {
        // 奇数列:从上到下
        row = pos_in_col + 1;
    } else {
        // 偶数列:从下到上
        row = n - pos_in_col;
    }

    cout << col << " " << row << endl;

    return 0;
}

代码解释:

  1. 输入处理:读取行数n、列数m和所有考生的成绩。

  2. 成绩排序:将成绩从高到低排序,使用greater<int>()实现降序排序。

  3. 确定排名:在排序后的成绩中找到小R成绩的位置,即排名。

  4. 计算座位

  5. 列号计算:(rank - 1) / n + 1(因为每列有n个座位)
  6. 在列中的位置:(rank - 1) % n
  7. 行号计算:根据列号的奇偶性决定方向
    • 奇数列:从上到下,行号 = 位置 + 1
    • 偶数列:从下到上,行号 = n - 位置

复杂度分析: - 时间复杂度:O(n×m log(n×m)),主要来自排序操作 - 空间复杂度:O(n×m),用于存储成绩副本

这种方法直接模拟了蛇形分配座位的过程,通过数学计算直接确定座位位置,效率较高。

【25CSPJ普及组】异或和

为了解决这个问题,我们需要从给定的非负整数序列中选出尽可能多的不相交区间,使得每个区间的异或和等于给定的非负整数k。关键在于高效地计算这些区间,避免暴力方法导致的高时间复杂度。

方法思路

  1. 问题分析:我们需要找到多个不相交的区间,每个区间的异或和等于k。直接检查所有可能的区间会非常低效,特别是当序列长度达到50万时。
  2. 关键观察:利用前缀异或和来简化计算。定义前缀异或和数组pre,其中pre[i]表示前i个元素的异或和。区间[l, r]的异或和可以表示为pre[r] XOR pre[l-1]。因此,问题转化为找到尽可能多的点对(i, j),使得pre[j] = pre[i] XOR k,且这些点对对应的区间不相交。
  3. 动态规划优化:使用动态规划来记录当前最多可选的区间数。维护一个数组last_dp,记录每个前缀异或和对应的最大区间数。遍历序列时,更新当前的最大区间数,并利用last_dp来高效查找符合条件的点对。

解决代码

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

const int MAX_VAL = (1 << 20);

int last_dp[MAX_VAL];

int main() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    memset(last_dp, -1, sizeof(last_dp));
    last_dp[0] = 0;
    int pre = 0;
    int dp = 0;

    for (int i = 1; i <= n; i++) {
        pre ^= a[i];
        int new_dp = dp;
        if (last_dp[pre ^ k] != -1) {
            new_dp = max(new_dp, last_dp[pre ^ k] + 1);
        }
        last_dp[pre] = new_dp;
        dp = new_dp;
    }

    cout << dp << endl;

    return 0;
}

代码解释

  1. 初始化last_dp数组初始化为-1,表示未出现的前缀异或和。last_dp[0]初始化为0,因为前缀异或和为0时对应0个区间。
  2. 处理序列:遍历每个元素,更新当前的前缀异或和pre。检查是否存在pre XOR klast_dp中,若存在则更新当前的最大区间数new_dp
  3. 更新记录:将当前前缀异或和对应的最大区间数记录到last_dp中,以便后续查询。
  4. 输出结果:最终dp存储的就是能选出的最多不相交区间数。

这种方法通过动态规划和前缀异或和优化,将时间复杂度降为O(n),适用于大规模数据输入。

【25CSPJ普及组】多边形

为了解决这个问题,我们需要计算从给定的木棍中选择若干根木棍,使得它们能拼成一个多边形的方案数。拼成多边形的条件是至少选择三根木棍,且所有木棍的总长度大于最长木棍长度的两倍。

方法思路

  1. 问题分析:关键在于找出所有满足条件的木棍组合,即总长度超过最长木棍两倍且至少三根木棍的组合。
  2. 关键观察:利用动态规划和排序来高效计算不满足条件的组合数,然后从总组合数中减去这些无效组合。
  3. 算法选择
  4. 排序:将木棍按长度排序,以便系统性地处理每个木棍作为潜在最大值的情况。
  5. 动态规划:使用动态规划来统计前i根木棍的子集总和不超过当前木棍长度的方案数。
  6. 组合数学:计算所有可能的子集总数,并减去无效子集数(少于三根木棍或总长度不超过最长木棍两倍的子集)。

解决代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;

const int MOD = 998244353;
const int MAX_SUM = 5000;

long long power(long long base, long long exp) {
    long long result = 1;
    while (exp > 0) {
        if (exp % 2 == 1) {
            result = result * base % MOD;
        }
        base = base * base % MOD;
        exp /= 2;
    }
    return result;
}

int main() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    sort(a.begin(), a.end());

    long long total_subsets = power(2, n);
    long long subtract = (1 + n + (long long)n * (n - 1) / 2) % MOD;
    long long total_valid = (total_subsets - subtract + MOD) % MOD;

    vector<long long> dp(MAX_SUM + 1, 0);
    dp[0] = 1;
    long long invalid = 0;

    for (int i = 0; i < n; i++) {
        int s = a[i];
        long long cnt = 0;
        for (int j = 0; j <= s && j <= MAX_SUM; j++) {
            cnt = (cnt + dp[j]) % MOD;
        }
        invalid = (invalid + cnt - 1 - i + MOD) % MOD;

        for (int j = MAX_SUM; j >= s; j--) {
            dp[j] = (dp[j] + dp[j - s]) % MOD;
        }
    }

    long long ans = (total_valid - invalid + MOD) % MOD;
    cout << ans << endl;

    return 0;
}

代码解释

  1. 输入处理:读取木棍数量和各木棍长度,并对木棍长度进行排序。
  2. 总子集计算:使用快速幂计算所有可能的子集数(2^n),并减去少于三根木棍的子集数(1根、2根和空集)。
  3. 动态规划初始化:初始化动态规划数组dp,用于记录不同总长度的子集数。
  4. 无效子集计算:遍历每个木棍,统计以其为最大值时无效子集数(总长度不超过其两倍且至少三根木棍),并更新无效子集总数。
  5. 结果计算:从总有效子集数中减去无效子集数,得到最终结果并输出。

该方法通过动态规划和组合数学高效地解决了问题,确保在较大输入规模下仍能快速运行。

总结


1. [CSP-J 2025] 拼数 / number

  • 考点:字符串处理、排序、贪心算法
  • 算法要求
  • 从字符串中提取所有数字字符。
  • 对数字字符进行降序排序,以形成最大的正整数。
  • 注意处理前导零,但题目保证至少有一个1-9的数字,因此前导零问题不会出现。
  • 难度:简单
  • 关键点:直接排序数字字符即可得到最大数,无需复杂算法。

2. [CSP-J 2025] 座位 / seat

  • 考点:模拟、数学计算、蛇形矩阵
  • 算法要求
  • 将成绩从高到低排序,确定每个考生的排名。
  • 根据蛇形排列规则计算座位位置:奇数列从上到下分配,偶数列从下到上分配。
  • 通过排名直接计算行和列,公式为:列号 = (排名 - 1) / 行数 + 1,行号根据列号的奇偶性决定。
  • 难度:简单到中等
  • 关键点:理解蛇形排列的规律,并用数学公式快速定位座位。

3. [CSP-J 2025] 异或和 / xor

  • 考点:动态规划、前缀异或和、位运算
  • 算法要求
  • 使用前缀异或和数组简化区间异或和的计算(区间[l, r]的异或和 = pre[r] XOR pre[l-1])。
  • 维护一个动态规划数组last_dp,记录每个前缀异或和对应的最大不相交区间数。
  • 遍历序列时,更新当前最大区间数,利用last_dp查询满足条件的点对(即pre[i] XOR k的存在)。
  • 难度:中等
  • 关键点:利用前缀异或和将问题转化为寻找点对,并通过动态规划优化时间复杂度至O(n)。

4. [CSP-J 2025] 多边形 / polygon

  • 考点:组合数学、动态规划、排序
  • 算法要求
  • 计算所有可能子集数($2^n$),并减去无效子集数(少于三根木棍或总长度不超过最长木棍两倍的子集)。
  • 对木棍长度排序,以便系统性地处理每个木棍作为最大值的情况。
  • 使用动态规划统计子集和(背包问题),记录不同总长度的子集数。
  • 遍历每个木棍,统计以其为最大值时无效子集数,并从总有效子集数中减去。
  • 难度:中等
  • 关键点:结合组合数学和动态规划,高效计算满足条件的子集数,避免暴力枚举。

总体总结

  • 考点覆盖:字符串处理、模拟、数学计算、动态规划、位运算、组合数学。
  • 算法要求:排序、贪心、前缀和、动态规划、背包问题、位运算。
  • 难度分布:从简单到中等,适合CSP-J级别选手,需要掌握基础算法和数据结构。
  • 建议:对于初学者,应先熟练掌握排序、模拟和动态规划等基础算法,再逐步解决更复杂的问题。