试卷解析 · 理解每一步
提高组 CSP-S 2026 初赛模拟卷 10
CSPS-2026-R1-Mock-10
试卷解析总览,可直接查看每题答案与解析。
第 1 题(单选题,2 分)
在 NOI Linux 系统中,使用 g++ 编译 C++ 程序时,如果要生成可调试的可执行文件,应该使用选项()。
正确答案B
解析详情
-g 选项用于生成调试信息,便于使用 gdb 调试;-O2 指开启二级编译优化;-static 表示链接静态库;-Wall 显示所有警告。
第 2 题(单选题,2 分)
执行 ; ; 输出为()。
正确答案A
解析详情
将高四位与低四位交换,0xAB = 10101011,十六进制的一位对应四位二进制数,所以结果是 0xBA,默认输出十进制结果为 186。
第 3 题(单选题,2 分)
连接 n 个字符串,每个字符串的长度不大于 m,选择不同的实现方法会导致时间复杂度不一样。最坏情况下,该操作的时间复杂度是()。
正确答案B
解析详情
每次连接可能复制整个结果字符串,串长度可达到 O(nm),最坏情况下的总时间复杂度为 O(n²m)。
第 4 题(单选题,2 分)
已知某完全二叉树的层次遍历序列为 ACBGFED,则其中序遍历序列是()。
正确答案C
解析详情
层次遍历序列为 (A)(CB)(GFED),A 为根节点,左子树 C(GF),右子树 B(ED),中序遍历序列为 GCFAEBD。
第 5 题(单选题,2 分)
用两个栈模拟队列,如果 push 操作总是入栈 ,那么 pop 操作是()。
正确答案D
解析详情
这是经典方法,确保先进入的元素在 栈顶,实现 FIFO 特征。
第 6 题(单选题,2 分)
关于树的重心,下列说法中不正确的是()。
正确答案C
解析详情
树的重心是删除该节点后使剩余最大连通块的节点数最小的节点。可以证明,一棵树最多有两个重心,且它们必定相邻。树中到其他所有节点的距离之和最小的节点称为费马点。在无点权的情况下,可以证明费马点和重心是等价的。
第 7 题(单选题,2 分)
给定数字 0、1、5、6、7、9,每个数字最多用一次,可能组成( )个正偶数。
正确答案C
解析详情
一位偶数:只有数 6,计 1 个。
两位偶数:个位为0有5个,个位为6有4个,计9个。
三位偶数:个位为 0 有 20 个,个位为 6 有 16 个,计 36 个。
四位偶数:个位为 0 有 60 个,个位为 6 有 48 个,计 108 个。
五位偶数:个位为 0 有 120 个,个位为 6 有 96 个,计 216 个。
六位偶数:个位为 0 有 120 个,个位为 6 有 96 个,计 216 个。
结果为 。
第 8 题(单选题,2 分)
最长公共子序列(LCS)的应用不包括()。
正确答案D
解析详情
LCS 用于相似度比较,不适用于加密问题。
第 9 题(单选题,2 分)
给一个无向图着色,相邻节点不能同色,如果至少需要3种颜色,则该图()。
正确答案B
解析详情
二分图可用两种颜色染色,需要3种颜色说明不是二分图,因此包含奇环(路径长度为奇数的环)。
第 10 题(单选题,2 分)
同余方程组 , 的解为( )。
正确答案A
解析详情
最快的方法是直接将四个选项代入验证。正向解法例如中国剩余定理,设 ,代入第二个同余式得 ,即 。3 在模 5 下的逆元为 2,故 。令 ,则 ,因此解为 。
第 11 题(单选题,2 分)
下列几种排序算法中,在平均情况下,( )的时间复杂度与其他三种不同。
正确答案A
解析详情
在平均情况下,堆排序、归并排序和快速排序的时间复杂度均为 ,选择排序为 。
第 12 题(单选题,2 分)
假设有 5 名同学,i 号同学的座位为 i 号课桌。考试时为了防止作弊,每个同学不能坐在自己的位置上,则考试时 5 名同学共有( )种坐法。
正确答案B
解析详情
错排列问题,递推式为 , , , , , 。另一种做法是容斥原理:从总排列的方案数中,减去至少有1名同学在原来位置上的排列数,加上多减去的至少有2名同学在原来位置上的排列数,再减去至少有3名同学在原来位置上的排列数,以此类推,交替加减来纠正重复计数。 。
第 13 题(单选题,2 分)
哈希表长度为 13,哈希函数 ,采用线性探查法解决问题。已依次插入关键码 。现要查找关键码 91,需要探查的桶的下标依次是(包括第一次计算哈希地址)()。
正确答案B
解析详情
所有数都是 13 的倍数,故从第二个数开始哈希值都存在冲突而需要后移。91 为第 6 个数,存放在下标为 5 的位置,所以探查时,也是从下标 0 开始,直到 5 结束,故选 B。
第 14 题(单选题,2 分)
以下代码执行后,输出的结果是()。
int a = 0, b = 1;
for (int i = 1; i <= 6; i++) {
if (i % 2 == 1) {
a = a + b;
} else {
b = a + b;
}
}
cout << a << " " << b << endl;正确答案B
解析详情
可以直接模拟求解。交替更新 a 和 b 其实是用滚动存储的方式求斐波那契数列。循环 6 次后,a 和 b 分别对应斐波那契数列的第 7 项(8)和第 8 项(13)。
第 15 题(单选题,2 分)
抛掷一枚均匀骰子,直到出现2次6为止,期望的抛掷次数是()。
正确答案B
解析详情
6个值出现的期望值显然应该一样,即都是2,而期望值总和显然等于次数,所以 。严谨解法可以用经典的期望动态规划: 已经投出i个6,到出现2次6还需投掷的期望次数,则 ,本题中边界条件为 ,则 。
二、阅读程序(1)
#include <iostream>
using namespace std;
int main() {
int n, p;
cin >> n >> p; // 保证 n 为正整数,p 为素数,cur 不会溢出 long long 类型
int ans = 0;
long long cur = p;
while (cur <= n) {
ans += n / cur;
cur *= p;
}
cout << ans << endl;
return 0;
}第 16 题(判断题,2 分)
输入 10 2 时,程序输出为 8. ()
正确答案正确
解析详情
(除法都向下取整,下同)。
第 17 题(判断题,2 分)
输出一定是正整数。 ()
正确答案错误
解析详情
如输入的 n < p,则输出为 0(while 循环不会执行)。
第 18 题(判断题,2 分)
程序的时间复杂度为 。 ()
正确答案正确
解析详情
cur 每次乘 p, 次后就会超过 n。
第 19 题(单选题,3 分)
如果希望从低位到高位依次输出 n 在 p 进制下的各位,ans += n / cur;这句应该改为( )。
正确答案D
解析详情
先通过除法去掉低位部分,再用 %p 拿到最低位。因为 cur 一开始初始化为 p,而那个位应该是 n / 1 % p,所以等价于先乘一个 p 再除以 cur。
第 20 题(单选题,3 分)
输入 n=100, p=5 时, 程序输出为( )。
正确答案B
解析详情
.
二、阅读程序(2)
#include <iostream>
using namespace std;
const int N = 100009;
int prime[N], tot, sd[N], sp[N];
bool vis[N];
void init() {
sd[1] = 1;
for (int i = 2; i < N; i++) {
if (!vis[i]) {
prime[++tot] = i;
sd[i] = i + 1;
sp[i] = i + 1;
}
for (int j = 1; j <= tot && i * prime[j] < N; j++) {
vis[i * prime[j]] = true;
if (i % prime[j] == 0) {
sp[i * prime[j]] = sp[i] * prime[j] + 1;
sd[i * prime[j]] = sd[i] / sp[i] * sp[i * prime[j]];
break;
} else {
sp[i * prime[j]] = prime[j] + 1;
sd[i * prime[j]] = sd[i] * sd[prime[j]];
}
}
}
}
int main() {
init();
int n;
cin >> n;
cout << sd[n] << endl;
return 0;
}第 21 题(判断题,2 分)
输入7,则输出是8。()
正确答案正确
解析详情
7 的约数只有 1 和 7。也可以强行根据代码,如果你知道线性筛,那么因为 7 是素数,容易看到所有素数 i 的 sp[i] = sd[i] = i + 1。
第 22 题(判断题,2 分)
init() 函数使用线性筛法标记素数,vis[i] 为 1 时,表示 i 是素数。 ()
正确答案错误
解析详情
vst[i] 为 0 时,表示 i 是素数。可以从 if (!vis[i]) { prime[++tot] = i; ⋯ } 看出。
第 23 题(判断题,2 分)
init() 函数预计算区间 [1, N - 1] 内每一个整数的所有素因数之和,并用数组 sd 记录。
正确答案错误
解析详情
算的是 n 的所有约数和,不跟素因数。如果看不懂,通过代入例如 n=2 模拟也可以判断。
第 24 题(单选题,4 分)
输入2026时,输出为()。
正确答案D
解析详情
2026 的约为:1、2、1013、2026,和为 3042。
第 25 题(单选题,4 分)
init() 函数的时间复杂度是()。
正确答案A
解析详情
复杂度同线性筛,所以是 ,原理是每个合数 x 只会被其最小素因数筛一次,也就是 全局只会执行一次。
二、阅读程序(3)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll T, a, m, ans;
ll quickpow(ll a, ll b) {
if (b < 0) return 0;
ll ret = 1;
a %= m;
while (b) {
if (b & 1) ret = (ret * a) % m;
b >>= 1;
a = (a * a) % m;
}
return ret;
}
ll inverse(ll a, ll m) {
return quickpow(a, m - 2);
}
int main() {
cin >> a >> m;
ans = inverse(a, m);
cout << ans << " ";
return 0;
}第 26 题(判断题,2 分)
输入 a=3, m=7, 输出是 5. ()
正确答案正确
解析详情
,强行验证也可以: 。
第 27 题(判断题,2 分)
输入 a=4, m=6, 输出是 4. ()
正确答案正确
解析详情
,但因为4和6不互素,所以结果没有逆元的含义(4模6也不存在逆元)。
第 28 题(判断题,2 分)
输入 m=2,无论 a 为何值,输出都是 1。 ()
正确答案正确
解析详情
m=2 时,调用 quickpow(a,0),while 循环不会执行,所以始终返回 1。但注意,只有当 a 是奇数时,1 才具备逆元的意义。
第 29 题(单选题,4 分)
输入的两个数分别为a和m,则程序的时间复杂度是()。
正确答案D
解析详情
实现时取决于快速幂计算 m 的复杂度。
第 30 题(单选题,4 分)
如果 inverse()函数是求 a 模 m 的逆元,下列说法中正确的是()。
正确答案C
解析详情
使用费马小定理求逆元。费马小定理表述为:m 为素数且 a 和 m 互素时, ,所以分一个 a 出来,剩下的 就是逆元了。这个结论在 m 不是素数时推广为欧拉定理,但逆元不再是 的形式;扩欧也是求逆元的一种手段,但并非本代码。
三、完善程序(1)
(树上第二类欧拉序)将树视为一个图,每条边视为2条(对向的)有向边,通过DFS遍历形成一个环游,遵循以下规则:从根节点开始遍历,每次进入一个节点u时,立即将其记录到序列中;每次从u的某个子节点返回到u时,再次记录u。当节点u有多个子节点时,按照子节点编号从小到大的顺序依次访问。最终得到的欧拉序列总长度为2n-1(n为树的节点数),其中每个节点出现的次数等于其在树中的度数(根节点除外,由于初始进入会多记录一次,因此出现次数为度数加一)。
#include <bits/stdc++.h>
using namespace std;
const int N = 1009;
vector<int> to[N];
int n, timer, euler[①];
void dfs(int u, int fa) {
++timer;
②;
for (int i = 0; i < to[u].size(); ++i)
if (to[u][i] != fa) dfs(to[u][i], u);
③;
}
int main() {
cin >> n; // 保证输入的 1 <= u, v <= n <= 1000,且无环
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
to[u].push_back(v);
to[v].push_back(u);
}
for (int u = 1; u <= n; ++u)
④;
dfs(1, 0);
for (int i = 1; ⑤; ++i) cout << euler[i] << " ";
cout << euler[2 * n - 1] << endl;
return 0;
}第 31 题(单选题,3 分)
①处应填()。
N - 1NN + 1N + N正确答案D
解析详情
欧拉序列长度为 2n-1,序列长度要定义为最大节点数的 2 倍。
第 32 题(单选题,3 分)
②处应填( )。
euler[timer] = ueuler[timer++] = ueuler[++timer] = ueuler[timer] = fa正确答案A
解析详情
进入 u 为根节点的子树时,记录节点 u 到序列中,timer 是一个时间戳,表示目前(第一次)访问到第几个节点,u 追加在欧拉序列尾部。因为上一行 timer 已经加过了,所以这里不需要再自增。
第 33 题(单选题,3 分)
③处应填( )。
euler[timer] = ueuler[timer] = faeuler[++timer] = ueuler[++timer] = fa正确答案D
解析详情
回溯完成节点 u 的子树遍历后,回到父节点时要记录父节点,这里 timer 没有提前自增,所以要在选项里自增。
第 34 题(单选题,3 分)
④处应填()。
sort(to, to + n)sort(to[u], to[u] + n)sort(to[u].begin(), to[u].begin() + to[u].size() - 1)sort(to[u].begin(), to[u].end())正确答案D
解析详情
对每个节点 u 的出边(to[u] 序列)按节点编号排序。sort 函数排序的参数区间为左闭右开 [begin, end),是 C++ 标准库的通用约定。对 vector 排序时,to[u].begin() 为第一个元素的迭代器,to[u].end() 为最后一个元素之后的位置的迭代器。选项 A 和 B 存在语法错误,无法编译通过;选项 C 中结束位置不对。
第 35 题(单选题,3 分)
⑤处应填( )。
i < n - 1i <= n - 1i < 2 * n - 1i <= 2 * n - 1正确答案C
解析详情
序列共 2n-1 个元素,根据输出的处理方式,最后一个元素单独输出,是处理行末不能出现空格的情况,所以 for 循环处理前 2n-2 个元素的输出。
三、完善程序(2)
(中国邮路问题)邮递员需要从邮局出发,遍历他所负责的所有街道(每条街道至少经过一次),最后返回邮局。每条街道有不同的长度,请设计一条最短的路线。本质是在一个具有非负权的赋权连通图 G 中,找出一条总权最小的环游。这种环游称为最优环游。若 G 是欧拉图,则 G 的任意欧拉环游都是最优环游。若 G 不是欧拉图,则 G 的任意一个环游必定经过某些边不止一次,需要通过添加重复边来构造欧拉图,且保证添加的边总权最小。
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
typedef long long ll;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> dst(n + 1, vector<int>(n + 1, INT_MAX));
vector<int> deg(n + 1, 0);
ll total = 0;
for (int i = 1; i <= n; ++i) dst[i][i] = 0;
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
if (①) {
dst[u][v] = w;
dst[v][u] = w;
}
deg[u]++;
deg[v]++;
total += w;
}
// Floyd-Warshall 算法:计算所有顶点对的最短路径
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i) {
if (dst[i][k] == INT_MAX) continue;
for (int j = 1; j <= n; ++j) {
if (dst[k][j] == INT_MAX) continue;
if (②)
dst[i][j] = dst[i][k] + dst[k][j];
}
}
}
vector<int> odd;
for (int i = 1; i <= n; ++i)
if (deg[i] % 2 == 1) odd.push_back(i);
if (③) {
cout << total << endl;
return 0;
}
int k = odd.size();
vector<long long> dp(1 << k, LLONG_MAX);
dp[0] = 0;
for (int mask = 0; mask < (1 << k); ++mask) {
if (dp[mask] == LLONG_MAX) continue;
int i = 0;
while (④) i++;
if (i >= k) continue;
for (int j = i + 1; j < k; ++j) {
if (mask & (1 << j)) continue;
if (dst[odd[i]][odd[j]] == INT_MAX) continue;
int new_mask = ⑤;
ll new_val = dp[mask] + dst[odd[i]][odd[j]];
if (new_val < dp[new_mask]) dp[new_mask] = new_val;
}
}
ll ans = total + dp[(1 << k) - 1];
cout << ans << endl;
return 0;
}第 36 题(单选题,3 分)
① 处应填( )。
w < dst[u][v]w <= dst[u][v]w > dst[u][v]w >= dst[u][v]正确答案A
解析详情
根据选项,这里是处理重边问题,重边中应保留最小的那条。注意这里是为后面 dst 以及匹配做准备的,新加的重复边必然是选最小的那条。而原问题中重边也是要重复访问(至少一遍),这个贡献算在 total 中了,所以 total 加的还是 w 本身,无论 w 是否比之前的 dst[u,v] 小。选项 B 虽然也不影响结果,但等于更新 dst 是浪费,所以选项 A 更好。
第 37 题(单选题,3 分)
② 处应填()。
dst[i][j] < dst[i][k] + dst[k][j]dst[i][j] <= dst[i][k] + dst[k][j]dst[i][j] > dst[i][k] + dst[k][j]dst[i][j] >= dst[i][k] + dst[k][j]正确答案C
解析详情
Floyd-Warshall 算法模板代码:更新 dst[i,j] 的条件是从 i 经 k 走到 j 比原先的最优解更优。严格说来选项 D 也没有问题,但相对而言选项 C 更好一些,因为等于也执行赋值是浪费。
第 38 题(单选题,3 分)
③ 处应填()。
odd.empty()!odd.empty()odd.size() % 2 == 0odd.size() % 2 == 1正确答案A
解析详情
本题最容易。odd 记录奇度顶点列表,当不存在奇度顶点时,存在欧拉回路,则最优解就是原始的边权和。顺便,在无向图中奇度顶点必然是偶数个,所以选项 C 永远成立,选项 D 不可能成立。
第 39 题(单选题,3 分)
④ 处应填()。
i < k && (mask | (1 << i))i <= k && (mask | (1 << i))i < k && (mask & (1 << i))i <= k && (mask & (1 << i))正确答案C
解析详情
DP 部分是实现对奇度顶点(必然是偶数个)进行两两配对,配对的代价是最短距离(dst)。这个问题是一般图最优匹配问题,最优解法是 (带花树),但这个算法太复杂了。本题用的是复杂度较高的状压 DP。状态 dp[mask] 表示将(二进制表示的)子集 S 中的奇度顶点配对的最小代价。每次选 2 个 mask 以外的顶点配对并转移。本题是找 mask 的二进制中最低位 0(编号最小的未匹配顶点),mask & (1 << i) 表示第 i 位是 1,还要继续找。k 是奇度顶点总数,这里编号是 0..k-1。所以 i < k 才是有效范围。注意 i 之所以可以任选为匹配过的顶点,是因为最后要求所有奇度顶点都要匹配上。
第 40 题(单选题,3 分)
⑤ 处应填( )。
(1 << i) | (1 << j)mask | (1 << i)mask | (1 << j)mask | (1 << i) | (1 << j)正确答案D
解析详情
状态更新。将顶点 i 和 j 标记为已匹配,加入原先的子集 mask,所以 i 和 j 两位都要更新为 1,用按位 or 这里相当于是加法。