1 条题解
-
0
CSP-S 2026 第一轮参考题解
本题解依据所给洛谷帖子中的 S 组题面整理,参考答案经独立推导及程序核验。判断题中,A 表示正确,B 表示错误。全卷43小题,共100分。
答案速查
题号 答案 分值 1—5 D D C D A 各2分 6—10 C A B A D 11—15 C C B C B 16—18 A A B 1分、1.5分、1.5分 19—21 C B C 各3分 22—24 A A B 各1.5分 25—27 B B D 各3分 28—30 A B B 各1.5分 31—33 A C C 各3分 34—38 C D B A C 39—43 C B D A A 一、单项选择题
1.D:8
x & (x - 1)每次清除二进制表示中最低的一个1,因此循环次数等于1的个数。,共有8个1,所以cnt = 8。2.D:102
哈夫曼树每次合并当前权值最小的两个结点。依次产生的新权值为
每次合并让两棵子树的全部叶子深度增加1,带权路径长度恰好增加两棵子树的权值和。因此答案为这些合并权值之和:。
3.C:301
先把 到 都写成三位数,不足处补0。在百、十、个位上,数字1各出现100次,合计300次。补上的前导0不会增加1的数量,且数字0自身不含1。最后,1000还含有一个1,故总数为301。
4.D:20
先选出恰好装对的两封信,有 种。余下三封必须全部装错,三元素错排共有2种。因此答案为 。不能让余下三封任意排列,否则会计入超过两封装对的情况。
5.A:29
,所以 。又因为 ,所以
6.C:34
令 表示把连续石堆 合成一堆的最小代价。最后一次合并一定发生在某个分界点 ,于是
$$f(l,r)=\min_{l\le k<r}\{f(l,k)+f(k+1,r)\}+\sum_{i=l}^r a_i,$$且 。按区间长度递推,长度2的代价依次是 ;长度3是 ;长度4是 ;最终
一种最优过程为:合并中间的 ,代价4;将最左的4与新产生的4合并,代价8;合并 ,代价7;最后合并 ,代价15。总计34。这里限制了相邻合并,不能直接套哈夫曼树的贪心。
7.A:3和4
查询前缀和时不断执行
i -= lowbit(i),访问下标 ,共3个。单点修改时执行i += lowbit(i),访问 ,共4个;下一项32越界,不再访问。8.B:8
先只考虑顶点1、2、3。顶点1必须排在另外两点之前,因此只有
1 2 3和1 3 2两种顺序。孤立点4可以插入每个序列的4个位置,得到 种拓扑序。9.A:
每次划分后两个子问题的规模之和仍为 ,所以递归树上每一层尚未终止的结点,其规模总和不超过 ,本层代价为 。最长分支每次缩小到原来的 ,层数为 ,总上界为 。
前 层中也有一段完整展开的层次:在深度小于约 时,即使沿最小的 子问题继续递归,也还没有到达常数规模。每个这样的完整层代价为 ,得到相同的下界。因此总复杂度为 。
10.D:直径7,重心1
从9到8的路径为 ,共有7条边,也是最长路径。删除结点1后得到两个大小均为4的连通块,最大连通块大小4不超过 ,因此1是重心。直径经过结点不代表这些结点都是重心;重心看的是删除后的连通块大小。
11.C:4
对含有多个结点的缩点DAG,使其强连通所需的最少添加边数为
每个源点都需要一条新入边,每个汇点都需要一条新出边,故答案至少是这两个数量的最大值;标准的强连通补边构造可以达到这个下界。本题为 。孤立点会同时计入源点与汇点,不能简单把两类数量相加。
12.C:132
设 为含 个结点、区分左右子树的二叉树数量。选择根后,若左子树有 个结点,右子树就有 个,因此
这就是卡特兰数,前几项为 ,故 。
13.B:6
长度2的前后缀都是
ab,长度4的前后缀都是abab。其余长度1、3、5、6、7、8均不匹配,而长度9是整个字符串,不是真前缀或真后缀。因此长度之和为 。14.C:把相等元素对也计入
修改后,仅当
a[i] < a[j]时取左边,否则取右边并增加mid - i + 1。进入else意味着a[i] >= a[j],而左半段已经有序,因此左边剩余元素都大于或等于当前右元素。这会统计全部满足原下标 且 的数对,包括原先没有计入的相等元素对。15.B:376
函数使用二进制快速幂,计算 。例如,,,,,所以
二、阅读程序
阅读(1):模2多项式除法
程序把输入串从高位到低位存入
a[0..31],再补12个0。数组gen对应13位串1100000001111,即多项式 。它的最高项系数为1。逐位处理
a[i]时,若它为1,就对齐除数并异或,消去这一位;若它已经为0,就跳过。模2多项式的减法等于异或,没有借位。最后保留的12位是余数,不能把这个过程误解为普通整数带借位的除法。16.A:正确
输入全0,补位也全0。每次都会被
continue跳过,没有任何位改变,最终输出12个0。17.A:正确
处理位置 时,该位如果为1,就与
gen[0]=1异或变为0;否则保持0。后续只会修改更靠后的位置,不会再访问它。所以a[0..31]最终全为0。18.B:错误
a定义在函数外,具有静态存储期,所有元素在程序开始时自动初始化为0。输入循环只改写a[0..31],因此a[32..43]本来就是0。删除这段显式补0代码,结果不变。若换成未初始化的局部数组,则不能使用这个结论。19.C:
gen[0]是除数最高位数组有13个元素。每次处理被除式的当前最高位
a[i]时,都将gen[0]与它对齐,所以下标越小,表示的位越高。20.B:补12个0后做模2除法
输入串右侧补12个0,在二进制位模式上相当于 ,在多项式表示中相当于乘 。随后执行模2除法,最终输出次数低于12的余数对应的12位串。
21.C:结果不再依赖输入
删去条件后,外层循环固定执行32次异或,每次修改的位置和掩码也固定。被输出的
a[32..43]初始均为0;它们只会被这些固定掩码改变,而不会从a[0..31]读取数据。因此输出与输入无关,实际固定输出为100000000101。所有异或下标仍在 — 内,不会越界。
a[0]只与1异或一次,输入首位为0时它最终为1,因此D不成立。阅读(2):ST表求区间最大公约数
对已建立的有效表项,
dp[i][j]表示长度为 、左端点为 的区间GCD。长度为 的区间拆成两个长度为 的相邻区间,因此第22行的转移正确。本题必须按原代码理解
$$lg[1]=0,\qquad lg[x]=\lceil\log_2x\rceil-1\quad(x\ge2).$$lg。它使用pw[t + 1] >= i,于是对于 ,令 ,有 。查询使用两端各一个长度为 的区间。这两段覆盖整个询问区间,没有空隙;当长度恰为2的幂时两段恰好相邻,其余时候可能重叠。GCD满足 ,重叠元素重复参与计算不会改变答案。长度1另由
lg[1]=0正确处理。22.A:正确
区间 中的数为 ,最大公约数为1。按程序,此时 ,两段为 和 ,其GCD分别为2和3,最终结果仍是1。
23.A:正确
时使用
lg[1]=0,两个表项都是dp[L][0]=a[L]。。24.B:错误
正整数的公约数不会大于任意一个数,所以区间GCD一定不大于区间最小值,并非一定不小于它。例如区间 的GCD是1,小于最小值2。
25.B:连续 个数的GCD
dp[i][0]对应一个元素。每提高一层,都把两个上一层区间合并,区间长度翻倍。因此dp[i][j]对应 。这里指程序实际建立、没有越过右端点的表项。26.B:
建表有 层,每层最多处理 个起点;前面若干层也都有 个有效起点。题目将一次GCD计算视为常数时间,因此总时间为 。这问只统计建表,不包含 次查询。当前排版中的完整建表范围是第17—24行。
27.D:
t=4时,,所以直到32仍取4。处理33时,条件 不成立,t增为5。之后直到64都满足 ,到65才增为6。因此范围是 。阅读(3):树形DP求直径
由于 ,从大编号向小编号处理时,一个结点的全部后代一定已处理完。
f[i]在结点 被处理时,是从它出发向子树内部走的最长距离;父结点当前的f则来自已经合并过的其他孩子。处理孩子 时,
f[fa[i]] + f[i] + 1把父结点已有的一条下行链,与经过当前孩子的新下行链拼起来。两条链来自不同的分支,可以组成一条简单路径。先尝试更新直径,再更新父结点的最长下行链,才能避免使用同一分支两次。每条最长路径都在其最高结点合并两侧分支时被考虑到,因此ans等于树的直径。时间、空间复杂度均为 。28.A:正确
输入构成链 ,最远的两点为1与5,其间4条边。
29.B:错误
f[1]是根到最远后代的距离,ans是任意两点间的最大距离。例如 ,父结点为1 1,树为2—1—3。此时f[1]=1,而ans=2。30.B:错误
如果先更新父结点的
f,再计算ans,父结点记录的最长链可能恰好来自当前孩子,导致把同一段路径用两遍。最小反例是 :原程序输出1;交换后先令f[1]=1,随后算出1+0+1=2,输出错误。31.A:直径的边数
ans枚举的是两条不同下行分支连接后的路径长度,最终对应全树最远两点的距离。根高度、叶子数和父编号之和都不是它维护的量。32.C:4
给定父数组形成一棵三层满二叉树。比如路径 长度为4,已经最长,因此输出4。
33.C:256
10个结点的树直径为9,当且仅当整棵树是一条链。由于父编号严格小于子编号,以1为根后,沿根的任一侧走向链端,编号必须严格递增。
将结点 分配到根的两侧,每侧按编号递增连接,就得到所有合法的链。9个结点各有2种归属,共 种。交换两侧得到的是同一个父数组,因此每个父数组恰好被计算两次,答案为 。允许一侧为空;交换后仍是两种不同的分配,不影响除以2。
三、完善程序
完善(1):平衡路线
把正边记为 ,负边记为 。一条路线的带符号和为 ,要最小化的是 。路线允许重复走边,这一点决定了做法。
先从 做BFS,记录最少边数
d,同时用c将每条树边两端染成不同颜色。p和ng分别记录 所在连通块中是否出现正、负边;ok记录该连通块是否为二分图。可以分三类处理:
- 不可达,输出 。
- 连通块内只有一种符号,任何路线的权值都等于经过的边数,故直接输出最短路长度
d[t]。 - 连通块同时含有两种符号,此时最小值只与路线长度的奇偶性有关。
第三类的原因可以完整地说明。来回走一次正边使带符号和增加2;来回走一次负边使它减少2。从 出发,选择一条闭合往返路线,让它能访问到某条正边和某条负边。这条固定往返路线的带符号和是偶数。在正、负边处分别额外往返若干次,就能把该闭合路线的带符号和调成任意指定的偶数。因此,可以在原路线前插入闭合路线,把原来的带符号和调成任意同奇偶性的整数。
每走一条边,不论符号为何,带符号和的奇偶性都会翻转。所以 与路线的边数同奇偶。若连通块是二分图,同色两点间的路线都是偶数长度,可以调到0;异色两点间都是奇数长度,绝对值最小为1。若连通块不是二分图,就存在奇环,绕行一次可改变奇偶性,再利用前面的偶数调整,总能得到0。
34.C:
op[0] == '+' ? 1 : -1后面用
w[i] > 0与w[i] < 0分别判断正、负边。只有C正确地保留两种符号。若把负边存成0,ng就永远不能被置为1。35.D:
hh < tt队列有效区间是左闭右开 。只有队首严格小于队尾时才有待处理结点。使用
hh <= tt会在队列已空时多取一次;与 比较也不能表达当前队列是否为空。36.B:
d[x] + 1首次沿 发现 ,它的最短距离是 的距离加一,这是无权图BFS的距离转移。
37.A:
c[y] == c[x]若一条边连接了相同颜色的两个结点,则二分图染色发生冲突,存在奇环,令
ok=0。边的正负号与图是否为二分图没有关系。重边也按同一规则处理;若有自环,同样会产生冲突。38.C:
!ok || c[s] == c[t]运行到这里时已经确认可达,且正负边都存在。非二分图能改变路线奇偶性,答案为0;二分图中同色端点之间也能调到0。仅剩二分图异色端点的情况输出1,因此条件为C。
补全后的参考代码
#include <iostream> constexpr int N = 200005; constexpr int M = 400005; int n, m, s, t; int h[N], e[M << 1], ne[M << 1], w[M << 1], idx; int q[N], d[N], c[N]; void add(int a, int b, int z) { e[idx] = b; w[idx] = z; ne[idx] = h[a]; h[a] = idx++; } int main() { std::cin >> n >> m >> s >> t; for (int i = 1; i <= n; i++) h[i] = d[i] = c[i] = -1; for (int i = 0; i < m; i++) { int a, b; char op[2]; std::cin >> a >> b >> op; int z = op[0] == '+' ? 1 : -1; add(a, b, z); add(b, a, z); } int hh = 0, tt = 0; int p = 0, ng = 0, ok = 1; q[tt++] = s; d[s] = c[s] = 0; while (hh < tt) { int x = q[hh++]; for (int i = h[x]; i != -1; i = ne[i]) { int y = e[i]; if (w[i] > 0) p = 1; if (w[i] < 0) ng = 1; if (d[y] == -1) { d[y] = d[x] + 1; c[y] = c[x] ^ 1; q[tt++] = y; } else if (c[y] == c[x]) ok = 0; } } if (d[t] == -1) { std::cout << -1; return 0; } if (!p || !ng) { std::cout << d[t]; return 0; } if (!ok || c[s] == c[t]) std::cout << 0; else std::cout << 1; return 0; }BFS只处理 所在连通块,整体时间复杂度为 ,邻接表、队列和数组占用 空间。即使别的连通块出现另一种符号,也不应该影响本题的判断。
完善(2):标准答案
直接枚举标准答案有 种,,不可行。学生数最多18,程序转而枚举每名学生对应的一个正负号,共 种。
设学生 在题 的作答对应 ,A为 、B为 ;标准答案对应 。两者相同得到1分,因此
令 ,便有 。利用恒等式 ,将目标乘以2后得到
$$\max_{z}\sum_i\left|c_i+\sum_jb_{ij}z_j\right| =\max_{s,z}\left(\sum_is_ic_i+\sum_jz_j\sum_is_ib_{ij}\right).$$固定学生符号 ,记 、。每道题可独立决定: 选A, 选B,等于0时任选。固定 的最优值就是
所以只要遍历全部 种学生符号,找到最大的
C + S,保存该组符号,再逐题恢复标准答案即可。ans保存的是原目标最优值的两倍,但程序最终只需要输出答案字符串,无须再除以2。为了减少更新量,程序用格雷码枚举学生符号。相邻格雷码只改变一个二进制位,即只翻转一个学生的 。这样无需重新计算整个 矩阵,只需更新第 名学生对各列的贡献。
39.C:
m - 2 * x[i]由 ,常数项必须是 。初始所有
s[i]为 ,故初始化时执行C -= c[i]。40.B:
mask ^ (mask >> 1)二进制反射格雷码为 。它按顺序枚举所有 位状态,且相邻两个状态只有一位不同。普通二进制计数在进位时会同时改变多位,不能直接用于此处的单行更新。
41.D:
__builtin_ctzll(d)d = g ^ lst只在被翻转的那一位上为1。末尾连续0的个数就是这一位从0开始的编号,对应需要改变的学生下标。d一定非零,满足函数对参数的要求。popcount(d)恒为1,无法定位该学生;对g本身求末尾0也无法定位此次变化。42.A:
2ll * s[k] * c[k]翻转前,学生 对
C的贡献是 ,翻转后是 ,变化量为 。由于代码写的是C -= ...,应填 。这里使用的是翻转之前的s[k];真正执行s[k] = -s[k]在后面。同理,列和改变 ,正好对应
q[j] -= 2ll * s[k] * v,再用新旧绝对值之差维护S。43.A:
v >= (n & 1)恢复答案时,
v就是对应列的 ,是 个 或 之和,因此与 同奇偶。- 为偶数时,条件变为
v >= 0,非负选A; 时两种答案一样好。 - 为奇数时, 不可能为0,最小的正值是1,所以
v >= 1恰好等价于 。
其他条件都存在反例:B在 为奇数且 时错选B;C在 为奇数且 时错选A;D在 为偶数时恒为真,即使 也会选A。因此只能选择A。
补全后的参考代码
#include <cstdlib> #include <iostream> #include <string> #include <vector> using namespace std; typedef long long ll; typedef unsigned long long ull; int main() { int n, m; cin >> n >> m; vector<ll> x(n), c(n); for (int i = 0; i < n; i++) { cin >> x[i]; c[i] = m - 2 * x[i]; } vector<string> a(n); for (int i = 0; i < n; i++) cin >> a[i]; vector<int> s(n, -1); vector<ll> q(m, 0); ll C = 0, S = 0; for (int i = 0; i < n; i++) { C -= c[i]; for (int j = 0; j < m; j++) { if (a[i][j] == 'A') q[j]--; else q[j]++; } } for (int j = 0; j < m; j++) S += abs(q[j]); ll ans = C + S; ull best = 0, lst = 0; for (ull mask = 1; mask < (1ULL << n); mask++) { ull g = mask ^ (mask >> 1); ull d = g ^ lst; int k = __builtin_ctzll(d); C -= 2ll * s[k] * c[k]; for (int j = 0; j < m; j++) { ll old = q[j]; int v = (a[k][j] == 'A' ? 1 : -1); q[j] -= 2ll * s[k] * v; S += abs(q[j]) - abs(old); } s[k] = -s[k]; if (C + S > ans) { ans = C + S; best = g; } lst = g; } for (int i = 0; i < n; i++) { if ((best >> i) & 1) s[i] = 1; else s[i] = -1; } string res(m, 'A'); for (int j = 0; j < m; j++) { ll v = 0; for (int i = 0; i < n; i++) { if (a[i][j] == 'A') v += s[i]; else v -= s[i]; } if (v >= (n & 1)) res[j] = 'A'; else res[j] = 'B'; } cout << res << endl; return 0; }初始化和恢复答案各需 ,格雷码枚举的每个状态用 更新,总时间为 ,空间为 。在 时,核心枚举约处理 个列更新;相较直接对每个状态重算所有学生,减少了一个 因子。
难度与训练定位
本包整数难度配置为 5(提高,独立估计)。这是对整卷完整理解难度的概括,不是官方评级,也不表示每道题都达到该难度。选择题主要考察组合计数、常见数据结构和复杂度;阅读程序侧重准确追踪状态、边界及算法不变量。两道完善程序的理解上限较高,分别涉及无向图路线的奇偶性,以及绝对值目标转换和格雷码增量枚举。
本卷本身按 CSP-S 第一轮客观题的结构组织,适合提高组第一轮训练。做题时应重点区分“认识算法名称”与“能解释这份代码为什么正确”:例如 ST 表的非标准对数边界、树形DP的更新顺序、格雷码恢复答案时的奇偶条件,都需要逐行理解。这个客观题包不用于衡量第二轮独立编程完成度,也不据此推测分数线。
- 1
信息
- ID
- 314
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 5
- 已通过
- 1
- 上传者