试卷解析 · 理解每一步
洛谷 SCP-S 2024 第一轮模拟赛(提高级 C++)
CSPS-2024-R1-Mock-Luogu
试卷解析总览,可直接查看每题答案与解析。
第 1 题(单选题,2 分)
下列编译命令,是 CSP-S 第二轮评测时用于编译题目英文名为 luogu 的传统型试题的选手代码的是( )。
正确答案B
解析详情
C++ 源文件应由 `g++` 编译,并显式采用 C++14 标准;评测常用 `-O2` 开启二级优化,再由 `-o` 指定输出文件。因此应选 B。
第 2 题(单选题,2 分)
在 2023 年的 CSP-S 第二轮认证中,有四位同学对于某一题得 0 分的情况提出了申诉。根据 CCF NOI 竞赛委员会的相关规定,下列申诉中可能被接受的是()。
正确答案D
解析详情
调试输出、随机化算法的偶然结果以及只在考场 Windows 环境通过,均属于选手应自行承担的程序问题。D 中程序在与评测环境相同的机器上以 850 毫秒完成且低于 1 秒限制,存在评测异常的可能,因而申诉可能被接受。
第 3 题(单选题,2 分)
大型语言模型(LLM)指使用大量文本数据训练的深度学习模型,它们可以生成自然语言文本或理解语言文本的含义。下列人工智能技术的应用中,不属于 LLM 的是()。
正确答案B
解析详情
ChatGPT、Gemini 和文心一言的核心能力都建立在大型语言模型上。Stable Diffusion 主要是面向图像生成的扩散模型,不属于大型语言模型。
第 4 题(单选题,2 分)
(47) 与下列()选项表示的数值一致?
正确答案C
解析详情
,与 相等。容易混淆的 。
第 5 题(单选题,2 分)
定义 在模 意义下的乘法逆元为同余方程 的解 。请计算 13 在模 29 意义下的乘法逆元是( )。
正确答案D
解析详情
验证 ,故 ,所以 13 模 29 的乘法逆元为 9。
第 6 题(单选题,2 分)
仿照二项式系数,定义三项式系数 为 的展开式中 项的系数。即: 。现要计算 的值,其结果为( )?
正确答案B
解析详情
所求是 的全部系数之和,令 即得 。
第 7 题(单选题,2 分)
当在二叉排序树中插入一个新结点时,若树中不存在与待插入结点关键字相同的结点,且待插入结点的关键字小于根结点的关键字,则新结点将成为根结点的( )?
正确答案A
解析详情
待插入关键字小于根关键字,第一次比较后必进入根的左子树;继续比较直到遇到空指针,新结点总是作为该位置的叶子结点插入。因此它是左子树中的某个叶子结点。
第 8 题(单选题,2 分)
一个盒子里有4个红球、5个蓝球和6个绿球。现在从盒子里随机抽取3个球,则这3个球中恰好有1个红球、1个蓝球和1个绿球的概率是()。
正确答案B
解析详情
有利取法为 ,全部取法为 。所求概率为 。
第 9 题(单选题,2 分)
对前缀表达式 - + 10 * 2 3 + 5 / 6 2 进行求值,得到的结果是()。
正确答案A
解析详情
表达式对应 。两部分分别为 16 和 8,所以结果为 。
第 10 题(单选题,2 分)
已知一个循环队列的存储空间为数组 data q[21] 且不浪费空间。且在队列的定义中,头指针指向队列的开头,尾指针指向队列末尾的下一个元素。现在若头指针和尾指针分别指向下标 8 和 3,则队列当前的长度为( )?
正确答案D
解析详情
数组容量为 21,且尾指针指向队尾后一格,因此队长为 。
第 11 题(单选题,2 分)
在使用下列算法对整数数列进行排序时,哪一个算法的运行时间复杂度与整数大小无关()。
正确答案C
解析详情
希尔排序的运行过程由元素个数、增量序列和比较结果决定,不把整数的数值范围或位数作为复杂度参数。计数排序依赖值域大小,基数排序依赖数字位数,因此选 C。
第 12 题(单选题,2 分)
假设变量 a, b, c 均为绝对值不超过 的 32 位有符号整型变量,下列关于这三个变量,说法正确的是( )。
正确答案C
解析详情
题设明确 `a` 是 32 位有符号整型,因此 `sizeof(a)` 为 4 字节。`auto d=a*b` 仍先按 `int` 运算并推导为 `int`;当 `c<0` 时乘法会反转大小关系,故含 `max` 的等式也不恒成立。
第 13 题(单选题,2 分)
对于以下代码,假设执行单次 `rand()` 的时间复杂度为 ,那么 `calc` 函数的运行时间复杂度为( )?
unsigned int ans = 0;
void val(int n) {
for (int i = 1; i <= n; ++i)
for (int j = 0; j < n; j += i)
ans += rand();
}
void calc(int n) {
if (n <= 1) val(n);
else calc(n / 2), calc(n / 2), val(n);
}正确答案C
解析详情
`val(n)` 的内层循环次数为 。于是 `calc` 满足 ;递归树各层求和后得到 。
第 14 题(单选题,2 分)
给定一个无向图 G,其中包含以下顶点和边:
顶点集合:
边集合:
请问以下哪一个选项正确描述了图 G 的双连通性?( )。
正确答案B
解析详情
顶点 E 只通过边 与其余图相连;删除 C 后,E 与其他顶点断开,所以 C 是割点,图 G 不是点双连通图。
第 15 题(单选题,2 分)
使用哈希表存储元素 19,53,49,114,为了让哈希表不发生哈希冲突,可以使用( )选项的哈希函数?(定义: 为不超过 的最大整数,如 )
正确答案C
解析详情
对 C 中的函数计算可得 、、、,四个地址互不相同。其余函数至少会让 19 与 53 或 49 与 114 发生冲突。
二、阅读程序(1)
#include <bits/stdc++.h>
using namespace std;
set<int> s1 = {1, 2, 3}, s2 = {2, 3, 1, 1},
s3 = {1, 2, 100}, s4 = {3, 4, 5};
map<set<int>, int> mp1, mp2;
set<int> intersection(set<int> a, set<int> b) {
if (a.size() > b.size()) swap(a, b);
set<int> res = a;
for (int i : a)
if (b.count(i) == 0)
res.erase(i);
return res;
}
int main() {
cout << (s1 == s2) << endl;
mp1[s3] = 9; mp1[s1] = 1;
mp2[s2] = 1; mp2[s4] = 5;
cout << (mp1 < mp2) << ' ';
cout << mp2[s3] << ' ';
cout << (mp1 <= mp2) << endl;
mp1.clear();
int n, l, t;
cin >> n;
for (int i = 1; i <= n; i++) {
set<int> tmp;
for (cin >> l; l; l--) {
cin >> t;
tmp.emplace(t);
}
mp1[tmp] = i;
}
set<int> res = mp1.begin()->first;
for (auto i : mp1)
res = intersection(i.first, res);
cout << res.size();
return 0;
}第 16 题(判断题,1.5 分)
代码运行后,输出的第一行是 0。()
正确答案错误
解析详情
`s2` 初始化时重复的 1 会被去重,且集合自动有序,因此 `s1` 与 `s2` 都等于 `{1,2,3}`。表达式 `s1 == s2` 为真,第一行输出 1 而不是 0。
第 17 题(判断题,1.5 分)
设集合 a, b 的元素个数分别为 p, q,则执行函数 intersection(a,b) 的时间复杂度为 。()
正确答案错误
解析详情
`a`、`b` 按值传参,复制两个集合本身就需要 ;随后还要遍历较小集合,并执行 `count` 和可能的 `erase`。例如 很大时,实际时间至少为 ,而题述只有 。
第 18 题(判断题,1.5 分)
将第32行的begin()换成rbegin(),运行结果可能改变。()
正确答案错误
解析详情
循环最终把 `res` 与 `mp1` 中每一个键集合都取交集。初值无论选最小键 `begin()->first` 还是最大键 `rbegin()->first`,它本身也会参与循环,最终结果都是所有键集合的交集。
第 19 题(判断题,1.5 分)
将第 33 行的 auto 换成 pair <set <int>, int>, 运行结果不变。()
正确答案正确
解析详情
`mp1` 的元素可转换并复制为 `pair<set<int>, int>`;循环体只读取副本 `i.first`,没有依赖键的 `const` 限定,也不修改映射。因此把 `auto` 换成该显式类型不改变结果。
第 20 题(单选题,3 分)
输出的第二行是()。
正确答案D
解析详情
`mp1` 与 `mp2` 的首项均为 `{1,2,3}:1`,第二项比较时 `{1,2,100}<{3,4,5}`,故 `mp1<mp2` 输出 1。`mp2[s3]` 插入值 0;再次比较时同为键 `s3` 而值 ,所以 `mp1<=mp2` 输出 0,整行为 `1 0 0`。
第 21 题(单选题,3 分)
记所有输入的1之和为L,则该程序最坏情况下的时间复杂度是()?
正确答案A
解析详情
读入的 个元素插入 `set`,总计至多 ;把集合插入 `map` 以及逐集合求交,同样可由 次量级的平衡树查找、插入或删除控制。最坏情况下总复杂度为 。
二、阅读程序(2)
#include <bits/stdc++.h>
using namespace std;
constexpr int mod = 998244353;
int n, k, a[12], b[12], ans[12], fa[12];
int findfa(int u) { return u == fa[u] ? u : fa[u] = findfa(fa[u]); }
int functionUnknown(int a[], int n) {
if (n <= 1) return 0;
int i = n - 1, j, k;
while (true) {
j = i; --i;
if (a[i] < a[j]) {
for (k = n; a[i] >= a[--k];);
swap(a[i], a[k]);
reverse(a + j, a + n);
return 1;
}
if (!i) {
reverse(a, a + n);
return 0;
}
}
return -1;
}
int F(int x) {
int ans = 0;
for (int i = k - 1; ~i; --i)
ans = (1ll * ans * x % mod + a[i]) % mod;
return ans;
}
int main() {
scanf("%d %d", &n, &k);
for (int i = 0; i < k; ++i) scanf("%d", a + i);
for (int m = 1; m <= n; ++m) {
for (int i = 0; i < m; ++i) b[i] = i;
do {
for (int i = 0; i < m; ++i) fa[i] = i;
int res = m;
for (int i = 0, u, v; i < m; ++i) {
u = findfa(i); v = findfa(b[i]);
if (u == v) continue;
--res; fa[u] = v;
}
int flag = 0;
for (int i = 0; i < m; ++i)
if (b[i] == i) flag = 1;
if (flag) continue;
ans[m] = (ans[m] + F(res)) % mod;
} while (functionUnknown(b, m));
printf("%d%c", ans[m], "
"[m == n]);
}
return 0;
}第 22 题(判断题,1.5 分)
functionUnknown 函数的返回值不可能为 -1。()
正确答案正确
解析详情
当 时函数直接返回 0;否则 `while(true)` 中要么找到下一排列并返回 1,要么确认已是最大排列、翻转后返回 0。循环后的 `return -1` 永远不可达。
第 23 题(判断题,1.5 分)
当输入的 k=1 时,输出的数值均能表示为 的形式,其中 t 是一个整数。()
正确答案错误
解析详情
时确有 `F(res)=a[0]`,但 `ans[m]` 每次累加后都会模 998244353,取模会破坏“是 `a[0]` 的整数倍”这一性质。例如 时,,并非 的整数倍。
第 24 题(判断题,1.5 分)
如果把第34行整行改为b[m - 1] = m - 1,则程序输出将会被改变。()
正确答案错误
解析详情
原第 34 行把 `b[0..m-1]` 初始化为升序。上一轮枚举结束时,`functionUnknown` 已把前 项从最大排列翻回升序;再执行 `b[m-1]=m-1` 后,长度 的数组仍恰为升序初始排列,所以输出不变。
第 25 题(单选题,3 分)
在主函数中,findfa 函数被调用的次数与以下哪个选项同阶()。
正确答案B
解析详情
固定 时共有 个排列;每个排列的长度为 ,循环中对每个 `i` 显式调用两次 `findfa`,调用量与 同阶。对 到 求和由末项支配,得到 。
第 26 题(单选题,3 分)
对于以下的输入数据,输出结果为( )。
5 5
1 2 3 4 5
正确答案C
解析详情
`res` 是排列的环数,且 ,所以 。 的无不动点排列含 6 个四环和 3 个双二环,得 ; 含 24 个五环和 20 个“三环+二环”,得 ,整行是 `0 15 30 477 2940`。
第 27 题(单选题,3 分)
functionUnknown 的功能接近于 C++ STL 库中的以下()函数。
正确答案A
解析详情
函数从右向左找首个上升位置,交换右侧恰好更大的元素,再反转后缀,得到字典序的下一个排列;若不存在上升位置,则把整个序列翻为最小排列并返回 0。这正是 `next_permutation` 的行为。
二、阅读程序(3)
#include <bits/stdc++.h>
using namespace std;
unsigned f(unsigned x) {
x ^= x << 3;
x ^= x >> 5;
return x;
}
unsigned g(unsigned x) {
return (x >> 5) ^ (x >> 2) ^ x ^ (x << 3);
}
int main() {
unsigned y, l, r;
cin >> y >> l >> r;
for (unsigned long long i = l; i <= r; i++)
if (f(i) == y)
cout << i % 11 << endl;
return 0;
}第 28 题(判断题,1.5 分)
若输入为 `2024 1 -1`,那么程序将不会输出任何数字。( )
正确答案错误
解析详情
读入 `-1` 时,无符号变量 `r` 得到 ,循环覆盖全部 32 位无符号数。两次 `x ^= shift` 都是可逆变换;对 求逆得到唯一的 ,程序会输出 。
第 29 题(判断题,1.5 分)
若把第14行i的类型改为unsigned int,则程序可能无法结束运行。()
正确答案正确
解析详情
若 `i` 改为 32 位 `unsigned int` 且 `r=4294967295`,当 `i` 取到最大值后执行 `i++` 会回绕为 0。此后条件 `i<=r` 始终成立,循环无法结束。
第 30 题(判断题,1.5 分)
对于所有符合要求的输入,输出的数字不可能超过一个。()
正确答案正确
解析详情
在固定 32 位上,`x ^= x << 3` 和 `x ^= x >> 5` 都可由低位到高位或由高位到低位逐位还原,因此 `f` 是双射。给定 `y` 只有一个 32 位原像,区间 `[l,r]` 中至多命中一次。
第 31 题(单选题,3 分)
若从所有 unsigned int 当中随机均匀选取一个 x,则 f(x)=g(x) 成立的概率和()的差最小。
正确答案B
解析详情
展开可知 `f(x)` 与 `g(x)` 只在 `(x<<3)>>5` 相比 `x>>2` 丢失的 3 个高位贡献上不同,因此相等当且仅当 `x` 的最高 3 位全为 0,概率为 。它与 的差为 ,小于与 0、、1 的差。
第 32 题(单选题,4 分)
定义 表示对 用函数 作用 次的结果,例如 。
假设 unsigned 类型具有 个二进制位,且已知存在某种计算 的算法的时间复杂度为 ,其中 是一个关于 的幂函数。则 最小是( )?
正确答案B
解析详情
`f` 是 位向量空间上的线性变换,可表示为 的二进制矩阵。用二进制快速幂计算该矩阵的 次幂,需要 次矩阵合成,每次代价由关于 的幂函数吸收,因此最小的 为 。
第 33 题(单选题,3 分)
当输入为 50 1 4294967295 时,输出的第一个数是()。
正确答案D
解析详情
令 ,由 逐段恢复得 ;再逆向恢复 ,得唯一原像 。于是首个输出为 。
三、完善程序(1)
(1)(最小垄断集)问题:你有一张 n 个结点(结点编号从 1 到 n),m 条边的无问无权图 ,其中 V 为点集,E 为边集。称 V 的一个子集 S 垄断了图 G,当且仅当 ,u, v 中必然恰有一个结点在 S 内。求垄断了 G 的子集 S 至少有多少个元素,或报告不存在这样的子集。试补全程序。
#include <bits/stdc++.h>
using namespace std;
int n, m, is[100005];
vector<int> g[100005];
queue<int> q;
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
int tot = 0;
for (int k = 1; k <= n; k++) {
if (①) continue;
q.emplace(k);
is[k] = 1;
int cnt[3] = ②;
while (q.size()) {
for (int i = 0; i < g[q.front()].size(); i++) {
int to = g[q.front()][i];
if (is[to] == 0) {
is[to] = ③;
cnt[is[to]]++;
q.emplace(to);
} else if (④) {
puts("No such subset."); // 报告不存在这样的子集
return 0;
}
}
q.pop();
}
tot += ⑤;
}
cout << tot;
return 0;
}第 34 题(单选题,3 分)
①处应填( )
is[k]is[k] == 0q.empty()tot > k正确答案A
解析详情
`is[v]=0` 表示未访问,1、2 表示二分图的两种颜色。外层枚举到已经染色的顶点时,应以 `if (is[k]) continue` 跳过,保证每个连通分量只启动一次 BFS。
第 35 题(单选题,3 分)
②处应填()
{0, 0, 0}{0, 0, 1}{1, 0, 0}{0, 1, 0}正确答案D
解析详情
每个新连通分量从顶点 `k` 开始,并已执行 `is[k]=1`,所以颜色 1 的计数初值为 1,颜色 2 为 0;`cnt[0]` 不参与答案,故应初始化为 `{0,1,0}`。
第 36 题(单选题,3 分)
③处应填( )
is[q.front()]3 - is[q.front()]!is[q.front()]q.front()正确答案B
解析详情
每条边的两个端点必须恰有一个属于集合,相邻点必须染成不同颜色。颜色只取 1 和 2,因此未访问邻点 `to` 应赋为 `3-is[q.front()]`,在 1 与 2 之间切换。
第 37 题(单选题,3 分)
④处应填( )。
is[to] == is[q.front()]is[to] != is[q.front()]is[to] && is[q.front()]abs(is[to] - is[q.front()]) == 1正确答案A
解析详情
若 `to` 已经染色,则当前边合法的条件是两端颜色不同;一旦 `is[to]==is[q.front()]`,该连通分量含奇环,无法让每条边恰有一个端点进入 ,应立即报告无解。
第 38 题(单选题,3 分)
⑤处应填( )。
cnt[1]min(cnt[0], cnt[1])min(cnt[1], cnt[2])cnt[1] + cnt[2]正确答案C
解析详情
一个合法连通分量的集合 必须恰好取二分图的一侧;交换两种颜色仍然合法。为使元素数最少,应在该分量贡献 ,各分量再累加。
三、完善程序(2)
(2)(最长公共前缀)定义 LCP(s,t)为两个字符串的最长公共前缀。例如:s=abcde,t=abcabc,则 LCP(s,t)=abc。定义|s|为字符串s的长度,在上一例中|s|=5。
现在给定了 n 个字符串 。现在需要统计所有同时满足如下两个条件的
之和对 998244353 取模的值:条件 1:i < j。条件 2: 。
若 , ,字符串仅由小写字母构成,试补全程序。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 9, mod = 998244353;
char s[maxn][22];
int n;
int tmp[27][maxn], a[maxn];
int dfs(int l, int r, int p) {
if (l >= r || p >= 20) return 0;
int tail[27], ans = 0;
memset(tail, 0, sizeof(tail));
for (int i = l; i <= r; ++i) {
int ch = ①;
②;
for (int j = 0; j < ch; ++j)
③;
}
int tot = l, pos = l;
for (int i = 0; i < 27; ++i)
for (int j = 0; j < tail[i]; ++j)
a[tot++] = tmp[i][j];
for (int i = 0; i < 27; ++i) {
④;
pos += tail[i];
}
return ans;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%s", s[i]);
for (int i = 1; i <= n; ++i) a[i] = i;
printf("%d
", ⑤);
return 0;
}第 39 题(单选题,3 分)
①处应填( )
s[i][p] ? s[i][p] - 'a' : 0s[i][p] ? s[i][p] - 'a' + 1 : 0s[a[i]][p] ? s[a[i]][p] - 'a' : 0s[a[i]][p] ? s[a[i]][p] - 'a' + 1 : 0正确答案D
解析详情
`a[i]` 才是当前位置对应的原字符串编号,所以要读取 `s[a[i]][p]`。字符串结束符必须映射为 0,字母 `a` 到 `z` 映射为 1 到 26,故表达式为 `s[a[i]][p] ? s[a[i]][p]-'a'+1 : 0`。
第 40 题(单选题,3 分)
②处应填( )
tmp[ch][tail[ch]++] = a[i]tmp[ch][++tail[ch]] = a[i]tmp[ch][tail[ch]++] = itmp[ch][++tail[ch]] = i正确答案A
解析详情
当前字符串编号是 `a[i]`,应写入字符桶 `tmp[ch]`;`tail[ch]` 既表示桶长又作为从 0 开始的下标,因此先写 `tmp[ch][tail[ch]]` 再自增,即 `tmp[ch][tail[ch]++]=a[i]`。这也保持了同桶元素的原下标顺序。
第 41 题(单选题,3 分)
③处应填( )
ans = (ans + 1ll * (p - 1) * tail[j] % mod) % modans = (ans + 1ll * p * tail[j] % mod) % modans = (ans + 1ll * (p + 1) * tail[j] % mod) % modans = (ans + 1ll * (p + 2) * tail[j] % mod) % mod正确答案B
解析详情
处理到位置 时,当前段内字符串已有长度 的公共前缀。对当前字符为 `ch` 的字符串,先前位于桶 `j<ch` 的每个字符串都满足原下标更小、字典序也更小,且二者恰在位置 首次不同,所以每对贡献 ,应累加 `p*tail[j]`。
第 42 题(单选题,3 分)
④处应填()。
ans = (ans + dfs(pos + 1, pos + tail[i], p + 1)) % modans = (ans + dfs(pos + 1, pos + tail[i], p - 1)) % modans = (ans + dfs(pos, pos + tail[i] - 1, p + 1)) % modans = (ans + dfs(pos, pos + tail[i] - 1, p - 1)) % mod正确答案C
解析详情
重排后,第 `i` 个字符桶恰占半闭区间意义下的连续段 `[pos,pos+tail[i])`,转换为函数的闭区间参数就是 `dfs(pos,pos+tail[i]-1,p+1)`。只有当前字符相同的字符串才需进入下一位继续比较;总深度至多 20。
第 43 题(单选题,3 分)
⑤处应填()。
dfs(0, n - 1, 0)dfs(1, n, 0)dfs(1, n, 1)dfs(1, n - 1, 1)正确答案B
解析详情
主程序把 `a[1..n]` 初始化为对应字符串编号,尚未比较任何字符,所以初始区间是 `[1,n]`,字符位置是 0,应调用 `dfs(1,n,0)`。每层稳定分桶且字符串长度至多 20,整体处理量为 量级。