CSP-J复赛真题讲解|2025-T1《拼数》T2-《座位》,怎样把基础题写稳?

四季读书网 7 0
CSP-J复赛真题讲解|2025-T1《拼数》T2-《座位》,怎样把基础题写稳?

CSP-J · 2025第二轮真题

“会做”之后,还要把程序写稳。

两道基础题,练清读题、思路和边界。

《拼数》 · 《座位》|原题+过程图解+完整代码

看到2025年CSP-J复赛前两题,不少孩子会觉得:“数字排个序,座位算个位置,好像都不难。”

但从知道大致做法,到把程序写得完整、正确,中间还隔着几处很容易漏掉的细节。

这篇不先给模板。我们按原题题面 → 思路推导 → 完整代码 → 易错点,把两道题一步步拆开。建议先停在题面处,自己想一遍,再向下看解析。

先读什么
再想什么
最后检查
原题与数据范围
《拼数》的数字选择
0与重复数字
输入输出与样例
《座位》的蛇形位置
换列与输出顺序

01

PART

T1《拼数》

NUMBER · 从题面到实现

原题题面 · 先读题,再看解析

题目描述

小 R 正在学习字符串处理。小 X 给了小 R 一个字符串 s,其中 s 仅包含小写英文字母及数字,且包含至少一个1~9中的数字。小 X 希望小 R 使用 s 中的任意多个数字,按任意顺序拼成一个正整数。注意:小 R 可以选择 s 中相同的数字,但每个数字只能使用一次。例如,若 s 为1a01b,则小 R 可以同时选择第1、3、4个字符,分别为1、0、1,拼成正整数101或110;但小 R 不能拼成正整数111,因为 s 仅包含两个数字1。

小 R 想知道,在他所有能拼成的正整数中,最大的是多少。你需要帮助小 R 求出他能拼成的正整数的最大值。

输入格式

从文件 number.in 中读入数据。输入的第一行包含一个字符串 s,表示小 X 给小 R 的字符串。

输出格式

输出到文件 number.out 中。输出一行一个正整数,表示小 R 能拼成的正整数的最大值。

样例1

输入

5

输出

5

s 仅包含一个数字5,因此小 R 仅能拼成正整数5。

样例2

输入

290es1q0

输出

92100

s 包含数字2、9、0、1、0。可以证明,小 R 拼成的正整数的最大值为92100。

数据范围(精简)

字符串长度:1 ≤ |s| ≤ 10⁶。仅含小写英文字母和数字,保证至少有一个非零数字。

时限1秒,内存512 MiB。提交文件:number.cpp。

思路分析|先决定选哪些,再决定怎样排

“任意多个、任意顺序”看起来像需要尝试很多方案,但百万字符的范围不允许枚举所有选择和排列。我们要先利用正整数的比较规则,把问题化简。

没有前导零时,先比较位数;位数相同时,再从高位比较数字。由此可以确定方向:先保留尽可能多的数字,再让较大的数字尽量靠前。

数字只有0~9十种,因此实现时不用逐个比较排序,统计次数,再倒序输出即可。下面分别说明这几个判断为什么成立。

第一步:为什么数字都要选?

题面写的是“任意多个数字”,但允许少选,不代表少选更优。题目保证至少有一个非零数字,我们可以把它放在最高位;在没有前导零的情况下,位数更多的正整数更大。

所以,所有数字字符都应该保留,包括0。比如从 a3b0092 中选出的数字是 3、0、0、9、2,只用 9、3、2 得到932;把两个0也留下,可以得到93200,明显更大。

第二步:同样的位数,为什么从大到小?

两个正整数位数相同,就从左向右比较。第一个不同的位置,谁的数字更大,谁就更大。因此应优先把较大的数字放在前面,最终顺序为 9、3、2、0、0。

CSP-J复赛真题讲解|2025-T1《拼数》T2-《座位》,怎样把基础题写稳?-第1张图片-四季读书网

第三步:统计十种数字,按次数输出

建立 cnt[10],统计每个数字出现的次数。遇到数字字符 ch,用 ch - '0' 得到数组下标;字母直接跳过。

示例计数

cnt[0] = 2;

cnt[2] = 1;

cnt[3] = 1;

cnt[9] = 1;

最后从9到0依次输出,每个数字输出它出现的次数。字符串可能长达100万字符,答案也可能有很多位,不能用 int 或 long long 保存整个数;使用字符串拼接或直接输出即可。

完整参考代码

number.cpp · C++14

#include <bits/stdc++.h>

using namespace std;

int main() {

 freopen("number.in", "r", stdin);

 freopen("number.out", "w", stdout);

 ios::sync_with_stdio(false);

 cin.tie(nullptr);

 string s;

 cin >> s;

 int cnt[10] = {}; // 各数字的出现次数

 for (char ch : s) {

  if (ch >= '0' && ch <= '9') {

   cnt[ch - '0']++; // 转为数字

  }

 }

 // 答案可能有百万位,不能用整数类型保存

 string ans;

 ans.reserve(s.size());

 // 从大到小拼接,重复数字与0都保留

 for (int d = 9; d >= 0; d--) {

  ans.append(cnt[d], char('0' + d));

 }

 cout << ans << '\n';

 return 0;

}

设输入长度为L,扫描和输出合计为O(L)时间。十个计数器本身只占常数空间;本实现保存输入字符串和答案,因此额外空间为O(L)。

容易丢分的三点:丢掉0、把重复数字去重、用整数变量累积答案。题目不是选出“不同的数字”,而是每个出现的位置都可以使用一次。

02

PART

T2《座位》

SEAT · 从排名到坐标

原题题面 · 先读题,再看解析

题目描述

CSP-J 2025第二轮正在进行。小 R 所在的考场共有 n × m 名考生,其中所有考生的 CSP-J 2025第一轮成绩互不相同。所有 n × m 名考生将按照 CSP-J 2025第一轮的成绩,由高到低蛇形分配座位,排列成 n 行 m 列。

具体地,设所有考生的成绩从高到低分别为 s₁ > s₂ > … > sₙₘ。成绩为 s₁ 的考生坐第1列第1行,成绩为 s₂ 的考生坐第1列第2行,……,成绩为 sₙ 的考生坐第1列第n行;成绩为 sₙ₊₁ 的考生坐第2列第n行,……,成绩为 s₂ₙ 的考生坐第2列第1行;成绩为 s₂ₙ₊₁ 的考生坐第3列第1行,以此类推。

例如,若 n=4、m=5,则所有20名考生按照成绩从高到低,根据下图中的箭头顺序蛇形分配座位。

CSP-J复赛真题讲解|2025-T1《拼数》T2-《座位》,怎样把基础题写稳?-第2张图片-四季读书网

给定小 R 所在考场座位的行数 n 与列数 m,以及所有考生第一轮的成绩 a₁、a₂、…、aₙₘ,其中 a₁ 为小 R 的成绩。你需要帮助小 R 求出,他的座位为第几列第几行。

输入格式

从文件 seat.in 中读入数据。第一行包含两个正整数 n、m,分别表示行数与列数。第二行包含 n × m 个正整数 a₁、a₂、…、aₙₘ,分别表示所有考生的成绩,其中 a₁ 为小 R 的成绩。

输出格式

输出到文件 seat.out 中。输出一行两个正整数 c、r,表示小 R 的座位为第c列第r行。

样例1

输入

2 2

99 100 97 98

输出

1 2

按成绩从高到低,100分坐第1列第1行,99分坐第1列第2行,98分坐第2列第2行,97分坐第2列第1行。小 R 得99分,因此坐第1列第2行。

样例2

输入

2 2

98 99 100 97

输出

2 2

座位分配顺序与样例1相同,小 R 得98分,因此坐第2列第2行。

样例3

输入

3 3

94 95 96 97 98 99 100 93 92

输出

3 1

数据范围(精简)

1 ≤ n、m ≤ 10,最多100名考生;1 ≤ aᵢ ≤ 100,所有成绩互不相同。

时限1秒,内存512 MiB。提交文件:seat.cpp。

思路分析|把成绩排序与座位定位分开

题目给出全部成绩,却只问小 R 一个人的位置。因此不必把整个考场的座位全部存下来,先确定他的成绩排第几,再将这个排名换成坐标即可。

成绩互不相同,比他高分的人数就能确定排名;每列坐n人,整除确定列号,取余确定列内位置,最后根据列号的奇偶决定从上还是从下数。

本题最多100人,排序后逐列模拟也能通过。这里选用统计排名+位置计算,重点练清编号、方向与边界,而不是追求复杂算法。

第一步:不必排序,先求自己的排名

座位只由成绩排名决定。成绩互不相同,因此排名=比自己分数高的人数+1。第一个读入的成绩就是小 R 的成绩,把它记为 target,继续读其他成绩并计数即可。

第二步:看清是按列走,不是按行走

假设考场有3行3列,把排名而不是分数填进格子,顺序如下。第一列向下,第二列向上,第三列再向下。

CSP-J复赛真题讲解|2025-T1《拼数》T2-《座位》,怎样把基础题写稳?-第3张图片-四季读书网

第三步:列号为什么要先减1?

把排名记为 k。每列容纳n人,但排名从1开始,所以先将它换成从0开始的编号 k - 1,再做整除和取余。

列号与列内偏移

int col = (k - 1) / n + 1;

int offset = (k - 1) % n;

col 是从1开始的列号,offset 是从0开始的列内偏移。奇数列向下:行号为 offset + 1;偶数列向上:行号为 n - offset。这里的除法是整数除法。

CSP-J复赛真题讲解|2025-T1《拼数》T2-《座位》,怎样把基础题写稳?-第4张图片-四季读书网

每列3人时,第3名还在第1列,第4名才进入第2列。如果直接用 k / n + 1,就会把恰好坐满一列的最后一人提前放进下一列。

用原题样例3走一遍

小 R 的成绩是94分,比他高的有95至100共6人,因此排名为7。考场有3行,计算过程如下。

计算
结果
排名
6+1=7
列号
(7−1)/3+1=3
列内偏移
(7−1)%3=0
第3列向下,行号
0+1=1
按“列、行”输出
3 1

完整参考代码

seat.cpp · C++14

#include <bits/stdc++.h>

using namespace std;

int main() {

 freopen("seat.in", "r", stdin);

 freopen("seat.out", "w", stdout);

 ios::sync_with_stdio(false);

 cin.tie(nullptr);

 int n, m;

 cin >> n >> m;

 int target; // 第一个成绩属于小R

 cin >> target;

 int rank = 1; // 排名=比自己高分的人数+1

 for (int i = 1; i < n * m; i++) {

  int score;

  cin >> score;

  if (score > target) rank++;

 }

 // 排名先减1,避免整列边界算错

 int col = (rank - 1) / n + 1;

 int offset = (rank - 1) % n;

 int row;

 // 奇数列向下,偶数列向上

 if (col % 2 == 1) {

  row = offset + 1;

 } else {

  row = n - offset;

 }

 // 输出顺序:列、行

 cout << col << ' ' << row << '\n';

 return 0;

}

扫描全部成绩,时间复杂度为O(n×m);只保存少量变量,额外空间为O(1)。

输出顺序是“列、行”,不是“行、列”。分母用行数n,而不是列数m;列的奇偶性决定上下方向。即使样例碰巧行列相等,也必须用不同的行列数再检查一次。

03

PART

拿纸算一遍,再看答案

CHECK · 三组小自测

看完解析后,不急着复制代码。试着自己回答下面的问题,确认理解的是规律,而不只是记住了某一行公式。

自测1|0与重复数字

输入字符串为 a7b070c2,《拼数》应该输出什么?需要保留几个0、几个7?

自测2|恰好坐满一列

《座位》考场有4行3列,某位考生排名第8。应输出哪两个数?如果排名是9呢?

自测3|只有一行

考场有1行5列,小 R 排名第4。偶数列向上是否还会改变他的行号?

答案与简析

① 输出 77200。两个7、两个0都要保留,再加上一个2。重复出现不等于只能选一次。

② 第8名输出 2 1;第9名输出 3 1。第8名恰好是第2列的最后一位,第9名才进入新列。

③ 输出 4 1。只有一行时,奇数列和偶数列的行号都为1,方向变化不会改变结果。

完整代码按2025年原题要求使用文件读写。在要求标准输入输出的练习平台上,需按平台说明去掉对应的两行freopen;不能把一种提交方式机械照搬到所有环境。

///

LAST

基础题,练的是把逻辑写完整

SUMMARY · 会做,也要写稳

《拼数》提醒我们:读到“任意多个”后,还要说明为什么全部保留更优,再选择适合百万字符的实现。

《座位》提醒我们:先明确变量含义和编号起点,再检查奇偶列、整列边界和输出顺序。

两道题都不需要冷门算法,但也不是“知道名字就能满分”。先独立分析,再编写代码,最后用重复、最小值和临界位置验证,才是把基础题写稳的训练。

不要只问“我会不会”,也问“我有没有验证”。

NOI官网:CSP-J/S 2025题目及数据

田浩然制作

把方法讲清楚,把程序写稳。

抱歉,评论功能暂时关闭!