【25CSPJ普及组】拼数
这是一个字符串处理问题,需要从给定的字符串中提取所有数字字符,然后组成最大的正整数。
思路:
-
从字符串中提取所有的数字字符
-
对数字字符进行排序(降序),这样最大的数字会在前面
-
注意:不能有前导零,所以如果第一个字符是'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;
}
代码解释:
-
提取数字:遍历字符串,使用
isdigit()函数识别数字字符,将它们存入vector中。 -
排序数字:使用
sort(digits.rbegin(), digits.rend())对数字进行降序排序,这样最大的数字会排在前面。 -
特殊情况处理:
- 如果没有数字(题目保证不会出现)
-
如果所有数字都是0(题目保证不会出现,因为至少有一个1-9的数字)
-
输出结果:将排序后的数字字符连接成字符串输出。
示例验证:
- 输入:"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;
}
代码解释:
-
输入处理:读取行数n、列数m和所有考生的成绩。
-
成绩排序:将成绩从高到低排序,使用
greater<int>()实现降序排序。 -
确定排名:在排序后的成绩中找到小R成绩的位置,即排名。
-
计算座位:
- 列号计算:
(rank - 1) / n + 1(因为每列有n个座位) - 在列中的位置:
(rank - 1) % n - 行号计算:根据列号的奇偶性决定方向
- 奇数列:从上到下,行号 = 位置 + 1
- 偶数列:从下到上,行号 = n - 位置
复杂度分析: - 时间复杂度:O(n×m log(n×m)),主要来自排序操作 - 空间复杂度:O(n×m),用于存储成绩副本
这种方法直接模拟了蛇形分配座位的过程,通过数学计算直接确定座位位置,效率较高。
【25CSPJ普及组】异或和
为了解决这个问题,我们需要从给定的非负整数序列中选出尽可能多的不相交区间,使得每个区间的异或和等于给定的非负整数k。关键在于高效地计算这些区间,避免暴力方法导致的高时间复杂度。
方法思路
- 问题分析:我们需要找到多个不相交的区间,每个区间的异或和等于k。直接检查所有可能的区间会非常低效,特别是当序列长度达到50万时。
- 关键观察:利用前缀异或和来简化计算。定义前缀异或和数组
pre,其中pre[i]表示前i个元素的异或和。区间[l, r]的异或和可以表示为pre[r] XOR pre[l-1]。因此,问题转化为找到尽可能多的点对(i, j),使得pre[j] = pre[i] XOR k,且这些点对对应的区间不相交。 - 动态规划优化:使用动态规划来记录当前最多可选的区间数。维护一个数组
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;
}
代码解释
- 初始化:
last_dp数组初始化为-1,表示未出现的前缀异或和。last_dp[0]初始化为0,因为前缀异或和为0时对应0个区间。 - 处理序列:遍历每个元素,更新当前的前缀异或和
pre。检查是否存在pre XOR k在last_dp中,若存在则更新当前的最大区间数new_dp。 - 更新记录:将当前前缀异或和对应的最大区间数记录到
last_dp中,以便后续查询。 - 输出结果:最终
dp存储的就是能选出的最多不相交区间数。
这种方法通过动态规划和前缀异或和优化,将时间复杂度降为O(n),适用于大规模数据输入。
【25CSPJ普及组】多边形
为了解决这个问题,我们需要计算从给定的木棍中选择若干根木棍,使得它们能拼成一个多边形的方案数。拼成多边形的条件是至少选择三根木棍,且所有木棍的总长度大于最长木棍长度的两倍。
方法思路
- 问题分析:关键在于找出所有满足条件的木棍组合,即总长度超过最长木棍两倍且至少三根木棍的组合。
- 关键观察:利用动态规划和排序来高效计算不满足条件的组合数,然后从总组合数中减去这些无效组合。
- 算法选择:
- 排序:将木棍按长度排序,以便系统性地处理每个木棍作为潜在最大值的情况。
- 动态规划:使用动态规划来统计前i根木棍的子集总和不超过当前木棍长度的方案数。
- 组合数学:计算所有可能的子集总数,并减去无效子集数(少于三根木棍或总长度不超过最长木棍两倍的子集)。
解决代码
#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;
}
代码解释
- 输入处理:读取木棍数量和各木棍长度,并对木棍长度进行排序。
- 总子集计算:使用快速幂计算所有可能的子集数(2^n),并减去少于三根木棍的子集数(1根、2根和空集)。
- 动态规划初始化:初始化动态规划数组
dp,用于记录不同总长度的子集数。 - 无效子集计算:遍历每个木棍,统计以其为最大值时无效子集数(总长度不超过其两倍且至少三根木棍),并更新无效子集总数。
- 结果计算:从总有效子集数中减去无效子集数,得到最终结果并输出。
该方法通过动态规划和组合数学高效地解决了问题,确保在较大输入规模下仍能快速运行。
总结
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级别选手,需要掌握基础算法和数据结构。
- 建议:对于初学者,应先熟练掌握排序、模拟和动态规划等基础算法,再逐步解决更复杂的问题。