GESP 客观题评测系统

试卷解析 · 理解每一步

2026 CCF CSP-S 第一轮(提高级 C++)

CSPS-2026-R1

试卷解析总览,可直接查看每题答案与解析。

一、单项选择题

共 15 题,每题 2 分,共计 30 分;每题有且仅有一个正确选项。

第 1 题(单选题,2 分)

执行下列代码后,cnt 的值是( )。

int x = 2026, cnt = 0;
while (x) {
    x &= x - 1;
    cnt++;
}
A.
6
B.
7
C.
11
D.
8

正确答案D

解析详情
答案 D二进制置位数
思路与推导
  1. 先理解 x & (x-1) 的作用:x 减 1 时,最低位的 1 变成 0,它右边的 0 全变成 1;再与原来的 x 按位与,就恰好清除了最低位的那个 1,其余高位保持不变。

  2. 2026 的二进制是 11111101010,其中有 8 个 1,所以需要清除 8 次才能变为 0。

  3. 也可以直接跟踪 x:2026 → 2024 → 2016 → 1984 → 1920 → 1792 → 1536 → 1024 → 0。每次循环 cnt 加 1,最终 cnt=8,选择 D。

第 2 题(单选题,2 分)

用权值 {1, 2, 3, 4, 5, 6, 7, 8} 构造哈夫曼树,其带权路径长度是( )。

A.
108
B.
96
C.
99
D.
102

正确答案D

解析详情
答案 D哈夫曼树
思路与推导
  1. 哈夫曼算法每次从“当前全部权值”中取最小的两个合并,并把新权值放回集合。

  2. 本题合并过程为:1+2=3;3+3=6;4+5=9;6+6=12;7+8=15;9+12=21;15+21=36。

  3. 每次合并都会使两个子树内所有叶子的深度增加 1,因此本次对带权路径长度的贡献,就是两棵子树的权值之和。把所有合并代价相加即可得到 WPL。

  4. 所以 WPL=3+6+9+12+15+21+36=102WPL=3+6+9+12+15+21+36=102,选择 D。

第 3 题(单选题,2 分)

把 1 到 1000 的所有整数按十进制写出,数字“1”总共出现了多少次( )。

A.
300
B.
271
C.
301
D.
320

正确答案C

解析详情
答案 C数位计数
思路与推导
  1. 把 0 到 999 都补成三位数,即 000、001、……、999。补在前面的数字只有 0,不会增加数字 1 的出现次数,因此这样计数与原问题的 1 到 999 等价。

  2. 固定个位为 1 时,百位、十位各有 10 种选择,共出现 10×10=10010\times10=100 次;固定十位或百位为 1,同理各出现 100 次。

  3. 因此 1 到 999 共出现 3×100=3003\times100=300 个 1。最后再计入 1000 中千位上的一个 1,总数为 301,选择 C。

  4. 例如 111 会在三个数位的统计中各贡献一次,这正是题目所要求的出现次数。

第 4 题(单选题,2 分)

将 5 封信随机装入 5 个写好地址的信封(每封一个),恰好有 2 封装对的方案数是( )。

A.
44
B.
24
C.
10
D.
20

正确答案D

解析详情
答案 D错排与组合计数
思路与推导
  1. “恰好有 2 封装对”可以分为两个独立步骤。

  2. 第一步,从 5 封信中选出装对的 2 封,有 (52)=10\binom{5}{2}=10 种选择,并将它们放入对应信封。

  3. 第二步,余下 3 封必须全部装错。把这三封信记作 A、B、C,按对应信封顺序写出装入的信,只有 BCA、CAB 两种排列没有任何一封装对。

  4. 由乘法原理,总方案数为 10×2=2010\times2=20,选择 D。若用 3!=63!=6 作为第二步的方案数,就会把有额外信件装对的情况也计入。

第 5 题(单选题,2 分)

32026 mod 1003^{2026}\bmod100 的值是( )。

A.
29
B.
9
C.
43
D.
81

正确答案A

解析详情
答案 A模幂周期
思路与推导
  1. 先找一个可以缩小指数的模幂周期。由 35=243≡43(mod100)3^5=243\equiv43\pmod{100},得到 310≡432=1849≡49(mod100)3^{10}\equiv43^2=1849\equiv49\pmod{100},再平方得 320≡492=2401≡1(mod100)3^{20}\equiv49^2=2401\equiv1\pmod{100}。

  2. 把指数写成 2026=20×101+62026=20\times101+6,则 32026=(320)101×36≡36(mod100)3^{2026}=(3^{20})^{101}\times3^6\equiv3^6\pmod{100}。

  3. 最后 36=7293^6=729,除以 100 的余数是 29,选择 A。这里不必证明 20 是最小周期,只要证明这个周期可用即可。

第 6 题(单选题,2 分)

有 5 堆石子排成一行,重量依次为 4、1、3、2、5。每次只能把相邻的两堆合并成一堆,代价为这两堆重量之和。将所有石子合并成一堆的最小总代价是( )。

A.
36
B.
35
C.
34
D.
33

正确答案C

解析详情
答案 C区间动态规划
思路与推导
  1. 由于只能合并相邻两堆,每个中间结果都对应原序列的一个连续区间。设 dp[l][r]dp[l][r] 为合并第 l 到第 r 堆的最小代价,单堆时 dp[i][i]=0dp[i][i]=0。

  2. 若最后一次在 k 与 k+1 之间分开,则先分别合并左右两段,再支付整个区间的重量和。因此 dp[l][r]=min⁡l≤k<r(dp[l][k]+dp[k+1][r])+∑i=lraidp[l][r]=\min_{l\le k<r}(dp[l][k]+dp[k+1][r])+\sum_{i=l}^{r}a_i。

  3. 按区间长度递推:长度 2 的代价依次为 5、4、5、7;长度 3 的代价为 12、10、15;长度 4 的代价为 20、21。

  4. 最后合并全部 5 堆时,区间总重量为 15。四个分割点对应总代价为 0+21+15=36、5+15+15=35、12+7+15=34、20+0+15=35,最小值是 34。

  5. 一种达到 34 的顺序是:先合并中间的 1 和 3,代价 4;再与左端 4 合并,代价 8;合并右侧 2 和 5,代价 7;最后合并 8 和 7,代价 15。合计 4+8+7+15=34,选择 C。

第 7 题(单选题,2 分)

树状数组维护长度 n=16n=16 的序列,查询前缀和 sum(11) 与单点修改 add(3, x) 分别需要访问树状数组中多少个下标( )。

A.
3 和 4
B.
4 和 4
C.
3 和 5
D.
4 和 3

正确答案A

解析详情
答案 A树状数组
思路与推导
  1. 树状数组中,lowbit(i)=i&(-i),表示 i 的二进制最低位 1 所对应的数值。

  2. 查询前缀和时执行 i-=lowbit(i):从 11 开始,lowbit(11)=1,转到 10;lowbit(10)=2,转到 8;lowbit(8)=8,转到 0 并停止。因此实际访问 11、10、8,共 3 个下标。

  3. 单点修改时执行 i+=lowbit(i):从 3 开始依次转到 4、8、16、32。由于 n=16,到 32 时停止,不再访问。

  4. 所以修改访问 3、4、8、16,共 4 个下标,选择 A。

第 8 题(单选题,2 分)

有向无环图 GG 顶点集为 {1,2,3,4}\{1,2,3,4\},边集为 {(1,2),(1,3)}\{(1,2),(1,3)\},顶点 4 与任何顶点均不相邻。该图不同的拓扑序共有多少种( )。

A.
12
B.
8
C.
4
D.
6

正确答案B

解析详情
答案 B拓扑排序
思路与推导
  1. 拓扑序要求每条有向边的起点出现在终点之前。两条边只要求 1 在 2、3 之前,2 与 3 之间没有先后限制。

  2. 先只排列 1、2、3,合法顺序是 123 和 132,共 2 种。

  3. 结点 4 是孤立点,可插入这三点顺序的任意一个空隙:最前面、第一与第二点之间、第二与第三点之间、最后面,共 4 个位置。

  4. 于是总数为 2×4=82\times4=8,选择 B。例如由 123 得到 4123、1423、1243、1234,另一种顺序同样产生 4 个不同结果。

第 9 题(单选题,2 分)

某分治算法满足 T(n)=T(n/3)+T(2n/3)+Θ(n)T(n)=T(n/3)+T(2n/3)+\Theta(n),T(1)=O(1)T(1)=O(1),则 T(n)T(n) 是( )。

A.
Θ(nlog⁡n)\Theta(n\log n)
B.
Θ(n2)\Theta(n^2)
C.
Θ(n1.5)\Theta(n^{1.5})
D.
Θ(n)\Theta(n)

正确答案A

解析详情
答案 A分治复杂度
思路与推导
  1. 用递归树分析:规模为 u 的问题拆成 u/3 和 2u/3,两者之和仍为 u,所以在尚未出现叶子的层上,该层所有子问题的规模之和为 n,非递归工作量为 Θ(n)\Theta(n)。

  2. 最长分支每次保留原规模的 2/3,经过 Θ(log⁡n)\Theta(\log n) 层降到常数,因此整棵递归树高度为 O(log⁡n)O(\log n)。每层工作量至多 O(n)O(n),总工作量上界为 O(nlog⁡n)O(n\log n)。

  3. 另一方面,最短分支每次保留 1/3,也需要 Θ(log⁡n)\Theta(\log n) 层才到常数。在最初这数量级的完整层中,每层都需要 Θ(n)\Theta(n) 工作量,给出 Ω(nlog⁡n)\Omega(n\log n) 的下界。

  4. 上下界一致,故 T(n)=Θ(nlog⁡n)T(n)=\Theta(n\log n),选择 A。不能直接把两个不等大的子问题当成两个 n/2 的子问题套式子。

第 10 题(单选题,2 分)

无根树含 9 个结点(编号为 1—9),边集为 {(1,2),(1,3),(2,4),(2,5),(3,6),(6,7),(7,8),(5,9)}\{(1,2),(1,3),(2,4),(2,5),(3,6),(6,7),(7,8),(5,9)\}。该树的直径(以边数计)与重心分别是( )。

A.
直径 6,重心为结点 3
B.
直径 7,重心为结点 2
C.
直径 8,重心为结点 1
D.
直径 7,重心为结点 1

正确答案D

解析详情
答案 D树的直径与重心
思路与推导
  1. 直径是树中最长简单路径的边数。先找叶子:4、8、9。三对叶子之间的距离分别为 dist(4,8)=6、dist(4,9)=3、dist(8,9)=7。

  2. 因此最长路径是 9—5—2—1—3—6—7—8,经过 8 个结点、7 条边,直径为 7。

  3. 重心要求删除该点后,每个剩余连通块的结点数都不超过全树的一半。删除结点 1 后,两个连通块为 {2,4,5,9} 和 {3,6,7,8},大小都是 4,满足 4≤9/24\le9/2。

  4. 若删除其他点,包含结点 1 及另一侧四个结点的连通块至少有 5 个结点,不能成为重心。因此唯一重心为 1,选择 D。

第 11 题(单选题,2 分)

一张有向图缩点后得到的有向无环图含 6 个顶点,其中入度为 0 的顶点有 3 个,出度为 0 的顶点有 4 个。为使原图变成强连通图,至少需要添加多少条有向边( )。

A.
7
B.
6
C.
4
D.
3

正确答案C

解析详情
答案 C强连通分量缩点
思路与推导
  1. 先将原图的每个强连通分量缩成一个点,内部已经互相可达,只需考虑这些分量之间如何连通。

  2. 缩点后的图有 6 个点,不能使用“本来就只有一个强连通分量”的特殊情况。每个零入度点都需要至少一条新增入边,否则其他分量无法到达它;每个零出度点都需要至少一条新增出边,否则它无法到达其他分量。

  3. 因此新增边数至少同时达到 3 和 4,也就是至少 4 条。一条从汇点连到源点的边,可以同时补上一个汇点的出边和一个源点的入边,所以两个数量不应相加。

  4. 对于含多个点的缩点 DAG,按可达关系适当安排汇点到源点的连接,可以用 max⁡(零入度点数,零出度点数)\max(\text{零入度点数},\text{零出度点数}) 条边达到强连通;这里是 max⁡(3,4)=4\max(3,4)=4,选择 C。注意不能随意配对,否则可能只连成多个互不连通的环。

第 12 题(单选题,2 分)

含 6 个结点的不同形态的二叉树共有多少棵(结点不带标号,区分左右子树)( )。

A.
42
B.
429
C.
132
D.
720

正确答案C

解析详情
答案 C卡特兰数
思路与推导
  1. 设 CnC_n 为 n 个无标号结点、区分左右子树的二叉树形态数,并规定空树 C0=1C_0=1。

  2. 固定根后,剩余 n-1 个结点分给左右子树。若左子树有 k 个结点,右子树就有 n-1-k 个,形态数为 CkCn−1−kC_kC_{n-1-k};枚举 k 得到卡特兰递推 Cn=∑k=0n−1CkCn−1−kC_n=\sum_{k=0}^{n-1}C_kC_{n-1-k}。

  3. 由此依次得到 C0,C1,…,C5=1,1,2,5,14,42C_0,C_1,\ldots,C_5=1,1,2,5,14,42,于是 C6=42+14+10+10+14+42=132C_6=42+14+10+10+14+42=132。

  4. 也可用通式 C6=17(126)=132C_6=\frac{1}{7}\binom{12}{6}=132,选择 C。这里统计形态,不需要再乘结点标号的排列数。

第 13 题(单选题,2 分)

字符串 S = "ababaabab",其所有既是真前缀又是真后缀的子串(非空)的长度之和是( )。

A.
4
B.
6
C.
7
D.
5

正确答案B

解析详情
答案 B字符串 border
思路与推导
  1. 字符串长度为 9,“非空真前缀”限定待检查长度为 1 到 8。

  2. 长度 2 时,开头和结尾都是 ab;长度 4 时,开头和结尾都是 abab,因此 2、4 都符合。

  3. 还要排除其他长度:长度 1 为 a 与 b,长度 3 为 aba 与 bab,长度 5 为 ababa 与 aabab,长度 6 为 ababaa 与 baabab,长度 7 为 ababaab 与 abaabab,长度 8 为 ababaaba 与 babaabab,均不相同。

  4. 所以所有符合条件的长度之和为 2+4=6,选择 B;长度 9 对应整个字符串,不满足“真”的要求。

第 14 题(单选题,2 分)

用归并排序统计逆序对,合并部分的核心代码为:

// 分并 a[l..mid] 与 a[mid+1..r],同时累加逆序对
if (a[i] <= a[j]) {
    tmp[k++] = a[i++]; // 取左半段元素
} else {
    tmp[k++] = a[j++]; // 取右半段元素
    ans += mid - i + 1;
}

若把判断条件中的 a[i] <= a[j] 改成 a[i] < a[j],则 ans 统计出的结果( )。

A.
完全不变
B.
变为原来的两倍
C.
变为满足 i<ji<j 且 a[i]≥a[j]a[i]\ge a[j] 的数对个数
D.
变为原来的一半

正确答案C

解析详情
答案 C归并与逆序对
思路与推导
  1. 合并时,左右两段都已经有序。原条件 a[i]<=a[j] 会在相等时优先取左段元素,因此只有 a[i]>a[j] 才进入 else,累加严格逆序对。

  2. 改为 a[i]<a[j] 后,进入 else 的条件变为 a[i]>=a[j]。由于左段有序,从 i 到 mid 的每个剩余元素都不小于 a[i],因而也都不小于当前右段元素 a[j],应计入 mid-i+1 对。

  3. 这些左段元素在原数组中的位置都早于右段元素;递归合并又会保证每一对跨段元素恰好在相应的一次合并中被计算。

  4. 于是最终统计的是原下标满足 i<j、数值满足 a[i]>=a[j] 的所有数对,选择 C。例如数组 [2,2] 原结果为 0,修改后为 1,新增的正是相等数对。

第 15 题(单选题,2 分)

执行 power(2, 100, 1000) 调用下列函数,返回值是( )。

long long power(long long a, long long b, long long p) {
    long long r = 1 % p;
    while (b) {
        if (b & 1)
            r = r * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return r;
}
A.
576
B.
376
C.
976
D.
176

正确答案B

解析详情
答案 B快速幂
思路与推导
  1. 函数是二进制快速幂:a 每轮平方,b 每轮右移;若当前 b 的最低位为 1,就把当前 a 乘入 r。因此它计算的是 2100 mod 10002^{100}\bmod1000。

  2. 把 100 拆成 64+32+464+32+4。连续平方并取模得到:24≡162^4\equiv16,28≡2562^8\equiv256,216≡5362^{16}\equiv536,232≡2962^{32}\equiv296,264≡616(mod1000)2^{64}\equiv616\pmod{1000}。

  3. 按照程序的低位到高位顺序,b 依次为 100、50、25、12、6、3、1,只有 b=25、3、1 时乘入 r,对应指数位 4、32、64。

  4. 所以 r 依次变为 16、16×296 mod 1000=73616\times296\bmod1000=736、736×616 mod 1000=376736\times616\bmod1000=376,最终返回 376,选择 B。

二、阅读程序(1)

程序输入不超过数组或字符串定义的范围;判断题正确填 √,错误填 ×;除特殊说明外,判断题 1.5 分,选择题 3 分,共计 40 分。

 1 | #include <iostream>
 2 | #include <string>
 3 | using namespace std;
 4 | int a[100];
 5 | string s;
 6 | int gen[13] = {1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1};
 7 | int main() {
 8 |     cin >> s;
 9 |     for (int i = 0; i < 32; ++i) {
10 |         a[i] = s[i] - '0';
11 |     }
12 |     for (int i = 32; i < 44; ++i) {
13 |         a[i] = 0;
14 |     }
15 |     for (int i = 0; i < 32; ++i) {
16 |         if (a[i] == 0) continue;
17 |         for (int j = 0; j < 13; ++j) {
18 |             a[i + j] ^= gen[j];
19 |         }
20 |     }
21 |     for (int i = 32; i < 44; ++i) {
22 |         cout << a[i];
23 |     }
24 |     cout << endl;
25 |     return 0;
26 | }

说明:输入保证为一个长度恰为 32 的 '0' / '1' 字符串。

第 16 题(判断题,1 分)

(1 分)当输入为 32 个 '0' 时,程序输出 12 个 0。( )

正确答案正确

解析详情
答案 正确模二除法
思路与推导
  1. 输入的前 32 位都是 0,第 12—14 行又将后 12 位设为 0,因此进入消元循环前 a[0] 到 a[43] 全为 0。

  2. 处理每个 i 时,a[i]==0 都成立,执行 continue 跳过整个异或循环,所以没有任何元素发生变化。

  3. 最后输出循环只访问 a[32] 到 a[43],共 12 个位置,输出为 000000000000。故断言正确。

第 17 题(判断题,1.5 分)

程序运行结束后,数组 a 中下标从 0 到 31 的元素一定全部为 0。( )

正确答案正确

解析详情
答案 正确消元不变量
思路与推导
  1. 考察消元循环处理位置 i 的瞬间:若 a[i]=0,程序直接跳过,该位保持 0;若 a[i]=1,则 j=0 时执行 a[i]^=gen[0],而 gen[0]=1,所以该位变为 1 XOR 1=0。

  2. 同一轮后续 j>0 的操作只影响 i 之后的位置。以后处理 i+1、i+2 等位置时,写入下标也只会更大,不会再次修改 a[i]。

  3. 因此“每轮结束后,a[0] 到 a[i] 全为 0”是循环不变量。32 轮结束后,a[0] 到 a[31] 必然全为 0,断言正确。

第 18 题(判断题,1.5 分)

若将第 12—14 行(为 a[32] 到 a[43] 补 0 的循环)删除,会改变程序输出的结果。( )

正确答案错误

解析详情
答案 错误全局数组初始化
思路与推导
  1. 数组 a 定义在 main 函数外,是具有静态存储期的全局数组;程序开始执行前,它的全部元素已经被初始化为 0。

  2. 读入循环只覆盖 a[0] 到 a[31],没有改动 a[32] 到 a[43]。所以即使删除显式补零循环,后 12 个元素在进入消元前仍然都是 0。

  3. 删除前后消元的初始数组完全一样,后续程序也一样,输出自然不会改变。因此“会改变结果”的断言错误。

  4. 本程序只处理一次输入;不能把这一结论直接推广到重复使用且已被修改的数组。

第 19 题(单选题,3 分)

关于第 6 行定义的数组 gen,下列说法正确的是( )。

A.
gen 共有 12 个元素,表示一个 12 位的除数
B.
gen 共有 13 个元素,表示一个 13 位的被除数
C.
gen 共有 13 个元素,其中 gen[0] 是除数的最高位
D.
gen 共有 13 个元素,其中 gen[12] 是除数的最高位

正确答案C

解析详情
答案 C多项式除法表示
思路与推导
  1. gen[13] 声明了 13 个元素,下标为 0 到 12,对应二进制串 1100000001111。

  2. 程序从左到右消去被除数的高位:当 a[i]=1 时,将 gen[0] 与 a[i] 对齐,gen[1] 与 a[i+1] 对齐,依此类推。gen[0]=1 正好用于消去当前最高的非零位,因此 gen[0] 是除数最高位。

  3. 若按多项式理解,gen 表示 x12+x11+x3+x2+x+1x^{12}+x^{11}+x^3+x^2+x+1,最高次数为 12,但系数共有 13 个。它是参与除法的生成多项式,而不是被除数。

  4. 所以选择 C;13 位除数最终留下的余数最多为 12 位,两者不要混淆。

第 20 题(单选题,3 分)

该程序实现的功能,最准确的说法是( )。

A.
将输入的 32 位串看成二进制数 MM,输出 MM 与 13 位二进制数 1100000001111 按位异或的结果
B.
将输入串视为 32 位二进制数 MM,在其后补 12 个 0(即计算 M×212M\times2^{12}),再对它做模 2 除法求余数,并输出 12 位余数
C.
对输入的 32 位串逐位取反并输出结果
D.
统计输入串中 1 的个数,并把该个数用 12 位二进制表示后输出

正确答案B

解析详情
答案 BCRC 与模二除法
思路与推导
  1. 先看输入如何变为被除数:程序把 32 位输入放在 a[0..31],再在后面补 12 个 0,得到一个 44 位串,即题目中所写的 M×212M\times2^{12}。

  2. 随后从最高位开始扫描。当前位为 0 时跳过;为 1 时,将 13 位 gen 与当前位置对齐并逐位异或,把当前最高位消为 0。这正是模 2 多项式长除法,其中减法使用 XOR,不产生借位。

  3. 全部 32 个位置处理后,前 32 位均为 0,后 12 位就是次数小于 12 的余式的系数,程序将它们完整输出。因此选择 B。

  4. 这里不是只与 gen 异或一次,也不是普通整数除法取模;模 2 除法的运算规则是关键。

第 21 题(单选题,3 分)

若把第 16 行 if (a[i] == 0) continue; 删除,说法正确的是( )。

A.
程序输出的结果不会改变
B.
可能造成程序运行错误
C.
程序能够正常输出一个 12 位 '0' / '1' 串,但是输出结果与输入的 s 无关
D.
程序运行结束后,a[0] 的值一定为 0

正确答案C

解析详情
答案 C固定异或序列
思路与推导
  1. 删除 continue 判断后,不论 a[i] 是 0 还是 1,每个 i=0..31 都会对 a[i..i+12] 执行一次固定的异或。

  2. 于是每个最终数组元素都等于“该位置初值”异或“所有覆盖该位置的 gen 元素”。覆盖哪些位置只由两个循环的下标决定,不再受输入影响。

  3. 程序输出的 a[32..43] 初值全为 0,且 XOR 不会把别的位置的 a 值传递过来,因此这 12 位完全由固定操作决定,与输入 s 无关,选择 C。逐位合并这些固定异或,输出为 100000000101。

  4. 下标最大为 31+12=43,没有数组越界;a[0] 最终是输入首位 XOR 1,未必为 0。

二、阅读程序(2)

 1 | #include <iostream>
 2 | using namespace std;
 3 | int n, m, a[100007], L, R, lg[100007], i, j, t, dp[100007][25], pw[25];
 4 | int gcd(int x, int y) {
 5 |     if (y == 0) return x;
 6 |     return gcd(y, x % y);
 7 | }
 8 | int main() {
 9 |     cin >> n >> m;
10 |     for (i = 1; i <= n; i++) cin >> a[i];
11 |     t = 0;
12 |     pw[0] = 1;
13 |     for (i = 1; i <= 24; i++) pw[i] = pw[i - 1] * 2;
14 |     for (i = 1; i <= 100000; i++)
15 |         if (pw[t + 1] >= i) lg[i] = t;
16 |         else { t++; lg[i] = t; }
17 |     for (i = 1; i <= n; i++) {
18 |         dp[i][0] = a[i];
19 |     }
20 |     for (j = 1; j <= lg[n]; j++) {
21 |         for (i = 1; i + pw[j] - 1 <= n; i++) {
22 |             dp[i][j] = gcd(dp[i][j - 1], dp[i + pw[j - 1]][j - 1]);
23 |         }
24 |     }
25 |     for (i = 1; i <= m; i++) {
26 |         cin >> L >> R;
27 |         cout << gcd(dp[L][lg[R - L + 1]], dp[R - pw[lg[R - L + 1]] + 1][lg[R - L + 1]]) << endl;
28 |     }
29 |     return 0;
30 | }

说明:保证 1≤n≤1000001\le n\le100000,每次查询满足 1≤L≤R≤n1\le L\le R\le n,且数组 aa 的元素均为正整数。

第 22 题(判断题,1.5 分)

当 n=5n=5,a={4,2,6,3,3}a=\{4,2,6,3,3\},且仅有一次查询 L=2,R=5L=2,R=5 时,输出为 1。( )

正确答案正确

解析详情
答案 正确区间 GCD
思路与推导
  1. 查询区间是 [2,5],包含 2、6、3、3,长度 x=4。注意本程序的对数表在 i=4 时仍满足 pw[2]>=4,所以 lg[4]=1,查询采用长度 21=22^1=2 的表项。

  2. 左段起点为 L=2,对应 dp[2][1]=gcd(2,6)=2;右段起点为 R−21+1=4R-2^1+1=4,对应 dp[4][1]=gcd(3,3)=3。

  3. 最终输出 gcd(2,3)=1,断言正确。这两段恰好拼成整个查询区间,因此这里虽然 lg 的定义与常见写法不同,仍能得到正确 GCD。

第 23 题(判断题,1.5 分)

当某次查询的区间长度为 1(即 L=RL=R)时,该次查询的输出一定等于 a[L]。( )

正确答案正确

解析详情
答案 正确单点区间
思路与推导
  1. 当 L=R 时,区间长度 R-L+1=1。对数表初始化 t=0,而 pw[1]=2>=1,所以 lg[1]=0。

  2. 左边取 dp[L][0];右边的起点为 R-pw[0]+1=R-1+1=R=L,因此右边也取 dp[L][0]。

  3. 建表时 dp[L][0]=a[L],故输出 gcd(a[L],a[L])=a[L],断言正确。虽然同一个数被合并了两次,但 GCD 具有幂等性,不会改变结果。

第 24 题(判断题,1.5 分)

任意一次查询的输出结果一定不小于该查询区间内的最小值。( )

正确答案错误

解析详情
答案 错误GCD 大小关系
思路与推导
  1. 程序查询的是区间内所有数的最大公约数。对于正整数,公约数必须整除每一个数,因此最大公约数不可能大于区间的最小值;题目中的“不小于”方向相反。

  2. 例如取长度为 2 的区间 [2,3]。此时 lg[2]=0,程序合并两个单点表项,输出 gcd(2,3)=1,而区间最小值为 2,满足 1<2。

  3. 一个反例即可否定“一定”的说法,所以断言错误。当区间最小值恰好整除所有其他数时,GCD 才等于这个最小值。

第 25 题(单选题,3 分)

对于 j≥1j\ge1,数组 dp[i][j] 保存的是( )。

A.
从 a[i] 开始连续 jj 个数的最大公约数
B.
从 a[i] 开始连续 2j2^j 个数的最大公约数
C.
a[i] 与 a[j] 的最大公约数
D.
从 a[1] 到 a[i] 的最大公约数

正确答案B

解析详情
答案 B稀疏表
思路与推导
  1. 先看初始状态 dp[i][0]=a[i],表示从 i 开始、长度为 20=12^0=1 的区间 GCD。

  2. 递推时,dp[i][j-1] 对应左半段 [i,i+2j−1−1][i,i+2^{j-1}-1],dp[i+2^{j-1}][j-1] 对应右半段 [i+2j−1,i+2j−1][i+2^{j-1},i+2^j-1]。

  3. 两段长度都为 2j−12^{j-1},首尾相接且没有遗漏。利用 GCD 的结合性,将两段 GCD 再求一次 GCD,就得到整个长度 2j2^j 区间的 GCD。

  4. 因此 dp[i][j] 保存从 a[i] 开始连续 2j2^j 个数的最大公约数,选择 B。例如 dp[i][2] 对应 4 个数,而不是 2 个数。

第 26 题(单选题,3 分)

若把一次求最大公约数的运算视为 O(1)O(1),则第 17—22 行建表过程的时间复杂度为( )。

A.
Θ(n)\Theta(n)
B.
Θ(nlog⁡n)\Theta(n\log n)
C.
Θ(n2)\Theta(n^2)
D.
Θ(mn)\Theta(mn)

正确答案B

解析详情
答案 B稀疏表建表复杂度
思路与推导
  1. 初始化第 0 层需要 n 次赋值,代价为 Θ(n)\Theta(n)。之后 j 从 1 增加到 lg[n],有效层数为 Θ(log⁡n)\Theta(\log n)。

  2. 在第 j 层,i 需满足 i+2j−1≤ni+2^j-1\le n,因此恰有 n−2j+1n-2^j+1 个合法起点。题目规定每次 GCD 按常数时间计算,所以总工作量为 n+∑j=1lg[n](n−2j+1)n+\sum_{j=1}^{\mathrm{lg}[n]}(n-2^j+1)。

  3. 各层最多处理 n 个起点,得到 O(nlog⁡n)O(n\log n) 上界;前面约 log⁡2n−1\log_2 n-1 层,每层又至少处理约 n/2 个起点,得到 Ω(nlog⁡n)\Omega(n\log n) 下界。

  4. 因此建表复杂度为 Θ(nlog⁡n)\Theta(n\log n),选择 B。查询次数 m 不参与建表,且本题不额外计入欧几里得算法自身的复杂度。

第 27 题(单选题,3 分)

设 xx 为一次查询的区间长度(即 x=R−L+1x=R-L+1),则使得 lg[x] = 5 的 xx 的取值范围是( )。

A.
[16,31][16,31]
B.
[17,32][17,32]
C.
[32,63][32,63]
D.
[33,64][33,64]

正确答案D

解析详情
答案 D非标准对数表
思路与推导
  1. 关键是第 15 行写的是 pw[t+1]>=i:当 i 恰好等于下一个 2 的幂时,t 还不增加;只有超过它才增加。

  2. 例如 lg[1]=lg[2]=0,lg[3] 到 lg[4] 为 1,lg[5] 到 lg[8] 为 2。由此可见,t=5 对应的区间是 25<i≤262^5<i\le2^6。

  3. 检查两端:i=32 时仍为 t=4;i=33 时增加到 5;i=64 时仍满足 64>=64,保持 5;i=65 时才增加到 6。

  4. 因此 lg[x]=5 当且仅当整数 x 属于 [33,64],选择 D。

二、阅读程序(3)

 1 | #include <iostream>
 2 | using namespace std;
 3 | int n, fa[100007], f[100007], ans;
 4 | int main() {
 5 |     cin >> n;
 6 |     for (int i = 2; i <= n; ++i) {
 7 |         cin >> fa[i];
 8 |     }
 9 |     for (int i = n; i >= 2; --i) {
10 |         if (f[fa[i]] + f[i] + 1 > ans) {
11 |             ans = f[fa[i]] + f[i] + 1;
12 |         }
13 |         if (f[i] + 1 > f[fa[i]]) {
14 |             f[fa[i]] = f[i] + 1;
15 |         }
16 |     }
17 |     cout << ans << endl;
18 |     return 0;
19 | }

说明:输入第一行为结点个数 nn,第二行为 n−1n-1 个整数,依次表示结点 2—nn 的父结点编号,满足 2≤n≤100002\le n\le10000 且 1≤fa[i]<i1\le fa[i]<i,根结点为 1。

第 28 题(判断题,1.5 分)

当 n=5n=5,fa[2]∼fa[5]={1,2,3,4}fa[2]\sim fa[5]=\{1,2,3,4\} 时,程序输出 4。( )

正确答案正确

解析详情
答案 正确树上动态规划
思路与推导
  1. fa[2..5]={1,2,3,4} 表示 2 接在 1 下、3 接在 2 下、4 接在 3 下、5 接在 4 下,整棵树是一条 1—2—3—4—5 的链。

  2. 全局数组 f 和 ans 初始为 0。倒序处理 i=5 时,候选值为 0+0+1=1,随后 f[4]=1;处理 i=4 时,候选值为 0+1+1=2,随后 f[3]=2。

  3. 继续处理 i=3、2,候选值依次为 3、4,f[2]、f[1] 也依次更新为 3、4。

  4. 所以最终 ans=4,断言正确;它统计路径边数,不是链上 5 个结点的数量。

第 29 题(判断题,1.5 分)

程序输出前,f[1] 的值一定等于 ans 的值。( )

正确答案错误

解析详情
答案 错误深度与直径
思路与推导
  1. f[1] 记录从根结点 1 向下出发能走到的最远距离,即树高;ans 则会把来自不同子树的两条路径接起来,记录任意两点之间的最长距离。

  2. 取最小反例 n=3、fa[2]=fa[3]=1。处理 i=3 后,f[1]=1、ans=1;再处理 i=2 时,候选值 f[1]+f[2]+1=1+0+1=2,ans 变为 2,而 f[1] 仍为 1。

  3. 对应最长路径是 2—1—3,两条边;根到任一叶子只有一条边。因此 f[1] 不一定等于 ans,断言错误。

第 30 题(判断题,1.5 分)

将第 10—12 行与第 13—15 行两个 if 语句的顺序交换后,程序输出结果不受影响。( )

正确答案错误

解析详情
答案 错误状态更新顺序
思路与推导
  1. 原程序先用 f[fa[i]]+f[i]+1 更新 ans,再把当前子树的高度加入父结点。此时旧的 f[fa[i]] 只来自已经处理的其他子树,或父结点自身,能与当前子树组成合法路径。

  2. 如果先更新父结点,f[fa[i]] 可能已经包含当前 i 的贡献;随后再加 f[i]+1,就可能把同一条分支重复计算两次。

  3. 例如 n=2、fa[2]=1:原顺序先得到候选 0+0+1=1,再令 f[1]=1,输出 1。交换后先令 f[1]=1,再算 1+0+1=2,错误地输出 2。

  4. 所以交换会影响结果,断言错误。状态合并时要区分“加入当前子树之前”和“加入之后”的含义。

第 31 题(单选题,3 分)

程序输出的 ans 表示的是( )。

A.
树中距离最远的两个结点之间路径所经过的边数
B.
根结点 1 到最远叶子结点之间路径所经过的边数
C.
树中叶子结点的个数
D.
所有结点的父结点编号之和

正确答案A

解析详情
答案 A树直径动态规划
思路与推导
  1. 条件 fa[i]<i 保证每个后代的编号都大于祖先,因此按 i 从 n 到 2 倒序处理时,当前结点 i 的全部子树都已处理完,f[i] 已是它到最远后代的距离。

  2. 设父结点为 p。合并 i 之前,f[p] 是 p 经过此前已处理分支能达到的最大距离;当前分支提供的距离为 f[i]+1。将两者相加,就得到一条经过 p、连接两个不同方向的合法路径。

  3. 程序先用这个候选值更新 ans,再更新 f[p],避免重复使用当前分支。任意最长路径都可在两端的最近公共祖先处拆成这样两段,所以枚举各子树合并时不会漏掉直径。

  4. 因此 ans 是树中距离最远两点之间的边数,选择 A;根到最远叶子的边数只是 f[1]。

第 32 题(单选题,3 分)

当 n=7n=7,fa[2]∼fa[7]={1,1,2,2,3,3}fa[2]\sim fa[7]=\{1,1,2,2,3,3\} 时,输出为( )。

A.
2
B.
3
C.
4
D.
5

正确答案C

解析详情
答案 C完全二叉树直径
思路与推导
  1. 父结点序列说明:根 1 的孩子为 2、3;2 的孩子为 4、5;3 的孩子为 6、7。因此左右两棵子树的高度都是 1,根到叶子的距离为 2。

  2. 倒序处理 7、6 后,f[3]=1,子树内部最长路径 6—3—7 长度为 2;处理 5、4 后,同理 f[2]=1。

  3. 处理 i=3 时,根的已有高度为 0,候选为 0+1+1=2,并更新 f[1]=2。再处理 i=2 时,候选为 2+1+1=4,ans 更新为 4。

  4. 一条实际达到该长度的路径是 4—2—1—3—6,共 4 条边,所以选择 C。

第 33 题(单选题,3 分)

当 n=10n=10,满足输出为 9 的合法输入种类数为( )。

A.
0
B.
9
C.
256
D.
512

正确答案C

解析详情
答案 C递增树与链计数
思路与推导
  1. 10 个结点的树一共只有 9 条边。要让直径达到 9,某条简单路径必须经过全部结点和全部边,所以整棵树必须是一条链,不能存在旁支。

  2. 由于每个结点满足 fa[i]<i,从根 1 向任意一端走时,编号必须严格递增。因此只要决定每个结点位于根的哪一侧,该侧的排列就只能按编号从小到大,父结点也随之唯一确定。

  3. 为避免左右两侧交换造成重复,可以规定结点 2 所在的一侧为固定侧。剩余结点 3 到 10 共 8 个,各自独立选择放在这一侧或另一侧;同侧按升序串接即可,全部满足父编号较小的条件。

  4. 于是共有 28=2562^8=256 种不同的父结点输入,选择 C。某侧为空也允许,此时根 1 位于整条链的端点。

三、完善程序(1)平衡路线

单选题,每小题 3 分,共计 30 分。

给定一张有 nn 个顶点、mm 条边的无向图,每条边带有符号 '+' 或 '-'。对于一条从顶点 ss 到顶点 tt 的路线,允许重复经过顶点和边。定义一条路线的权值如下:记 n+,n−n^+,n^- 分别为经过的 '+' 边数和经过的 '-' 边数,则该路线的权值为 ∣n+−n−∣|n^+-n^-|。

请计算从 ss 到 tt 的路线的最小权值。若不存在从 ss 到 tt 的路线,则输出 −1。

输入第一行为四个整数 n,m,s,tn,m,s,t。接下来 mm 行,每行给出两个整数 a,ba,b 和一个字符 '+' 或 '-',描述一条连接 aa 与 bb 的无向边及其符号。

数据满足 2≤n≤2×1052\le n\le2\times10^5,1≤m≤4×1051\le m\le4\times10^5,1≤s,t≤n1\le s,t\le n 且 s≠ts\ne t,1≤a,b≤n1\le a,b\le n,可能出现重边。

以下程序通过 BFS 求出最小权值。请补全程序。

#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 = /* ① */;
        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 (/* ② */) {
        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] = /* ③ */;
                c[y] = c[x] ^ 1;
                q[tt++] = y;
            } else if (/* ④ */)
                ok = 0;
        }
    }
    if (d[t] == -1) {
        std::cout << -1;
        return 0;
    }
    if (!p || !ng) {
        std::cout << d[t];
        return 0;
    }
    if (/* ⑤ */) std::cout << 0;
    else std::cout << 1;
    return 0;
}

第 34 题(单选题,3 分)

①处应填( )。

A.
op[0] == '+' ? 0 : 1
B.
op[0] == '+'
C.
op[0] == '+' ? 1 : -1
D.
op[0] == '-' ? 1 : 0

正确答案C

解析详情
答案 C正负边编码
思路与推导
  1. 路线的目标权值是正边经过次数减去负边经过次数后取绝对值,因此每次走正边应贡献 +1,走负边应贡献 -1。

  2. 后面 BFS 遍历边时,用 w[i]>0 设置 p=1,表示所在连通分量出现正边;用 w[i]<0 设置 ng=1,表示出现负边。因此两类边的编码必须分居 0 的两侧。

  3. 表达式 op[0]=='+' ? 1 : -1 正好满足要求,选择 C。它还会通过两次 add 把相同符号写入无向边的两个方向,反向走并不会把正边变成负边。

  4. 若把负边编码成 0,w[i]<0 永远不成立,程序会误判该分量没有负边,后面的分类讨论也就失效。

第 35 题(单选题,3 分)

②处应填( )。

A.
hh < n
B.
tt < n
C.
hh <= tt
D.
hh < tt

正确答案D

解析详情
答案 D数组队列
思路与推导
  1. 数组队列采用左闭右开的有效区间 [hh,tt):hh 指向下一项待取元素,tt 指向下一次入队应写的位置。

  2. 执行 q[tt++]=s 后,hh=0、tt=1,队列中有一个元素。只要 hh<tt,就至少还有一个元素可处理;取出时执行 q[hh++]。

  3. 当 hh==tt 时,所有已入队的元素均已取完,应结束循环。因此条件应为 hh<tt,选择 D。

  4. 例如图中只有起点所在的小连通分量,BFS 不会访问全部 n 个点,所以不能以 hh<n 或 tt<n 判断队列是否为空;hh<=tt 则会在空队列时多取一次。

第 36 题(单选题,3 分)

③处应填( )。

A.
d[y] + 1
B.
d[x] + 1
C.
d[x]
D.
d[x] - 1

正确答案B

解析详情
答案 BBFS 最短距离
思路与推导
  1. d[y]==-1 表示结点 y 尚未被 BFS 发现;当前从已出队的 x 沿一条边到达 y,因此这条路径的长度为 d[x]+1。

  2. BFS 按距离从小到大处理结点,第一次发现 y 时已经找到边数最少的路径,所以应赋值 d[y]=d[x]+1,选择 B。

  3. 这里 d 统计无权图中的边数,不累加边的正负号。当所在连通分量只有一种符号的边时,任意路线的绝对权值就等于其长度,程序才直接用 d[t] 作为答案。

  4. 赋值 d[y] 后立即入队,也保证同一个结点不会被反复加入队列。

第 37 题(单选题,3 分)

④处应填( )。

A.
c[y] == c[x]
B.
w[i] == 1
C.
c[y] != c[x]
D.
d[y] + 1 != d[x]

正确答案A

解析详情
答案 A二分图判定
思路与推导
  1. c 数组做二染色:起点颜色为 0,每发现一个新点 y,就令 c[y]=c[x]^1,使树边两端颜色相反。

  2. 如果 y 已经被访问,则需要检验当前这条边是否仍满足“两端异色”。若 c[y]==c[x],这条边连接了同色点,与二分图要求冲突,应令 ok=0。

  3. 从 BFS 树看,同色两点之间的树上路径长度为偶数,再加入当前这条边,就得到一个奇环;而二分图恰好不能含奇环。因此应填 c[y]==c[x],选择 A。

  4. 正负边只影响路线的符号和;二分图性质只由图的连接关系决定,与 w[i] 的正负无关。

第 38 题(单选题,3 分)

⑤处应填( )。

A.
ok && c[s] == c[t]
B.
ok && c[s] != c[t]
C.
!ok || c[s] == c[t]
D.
!ok && c[s] != c[t]

正确答案C

解析详情
答案 C带符号游走与奇偶性
思路与推导
  1. 执行到⑤时,程序已经排除了 t 不可达,以及连通分量中只有一种符号边的情况。现在 s、t 连通,而且该分量同时存在正边和负边。

  2. 先看奇偶性:每走一条边,符号和增加 +1 或 -1,两者模 2 都等于 1。因此符号和的奇偶性与路线长度相同。若图是二分图,s、t 同色时所有路线长度都为偶数,异色时都为奇数;若含奇环,则可以绕该奇环改变路线长度的奇偶性。

  3. 再说明为什么能把绝对值降到 0 或 1:取一条经过正、负两类边并最终到达 t 的路线。在经过正边时插入一次“沿该边走过去再走回来”,符号和增加 2;在负边处插入同样的往返,符号和减少 2。重复这些往返,就能在保持奇偶性的前提下把符号和调整到最靠近 0 的值。

  4. 为了经过两类边而增加的往返路段长度是偶数,不改变原路线奇偶性;若需要翻转奇偶性,可以再通过奇环。因此存在偶数长度路线时最小绝对值为 0,否则为 1。

  5. 所以输出 0 的条件是“不是二分图,或者 s、t 同色”,即 !ok || c[s]==c[t],选择 C。

三、完善程序(2)标准答案

给定 nn 名学生参加一场考试,考试共有 mm 道选择题,每道题只有 A、B 两个选项。

第 ii 名学生的作答为一个长度为 mm 的字符串。若最终公布的标准答案与该学生在某道题上的作答相同,则该学生在这道题上得 1 分,否则不得分。记第 ii 名学生最终得到的总分为 rir_i。

每名学生还有一个预期得分 xix_i。现在需要构造一份标准答案,使

∑i=1n∣ri−xi∣\sum_{i=1}^{n}|r_i-x_i|

尽可能大。

数据满足 1≤n≤181\le n\le18,1≤m≤3001\le m\le300,0≤xi≤m0\le x_i\le m。

提示:可以换一个角度处理 ∑i=1n∣ri−xi∣\sum_{i=1}^{n}|r_i-x_i|,把它写成更易优化的形式;对正整数 xx,__builtin_ctzll(x) 返回 xx 的二进制表示末尾连续 0 的个数;__builtin_popcountll(x) 返回 xx 的二进制表示中 1 的个数。

以下程序构造出一组满足要求的标准答案。请补全程序。

#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] = /* ① */;
    }
    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 = /* ② */;
        ull d = g ^ lst;
        int k = /* ③ */;
        C -= /* ④ */;
        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 (/* ⑤ */) res[j] = 'A';
        else res[j] = 'B';
    }
    cout << res << endl;
    return 0;
}

第 39 题(单选题,3 分)

①处应填( )。

A.
2 * x[i] - m
B.
-m + 2 * x[i] + 1
C.
m - 2 * x[i]
D.
m + 2 * x[i]

正确答案C

解析详情
答案 C绝对值优化展开
思路与推导
  1. 把学生的每道作答编码为 aij=+1a_{ij}=+1(A)或 −1-1(B),把标准答案编码为 yj=+1y_j=+1(A)或 −1-1(B)。两者相同时乘积为 1,不同时为 -1,因此该题是否得分可写为 (1+aijyj)/2(1+a_{ij}y_j)/2。

  2. 对 m 道题求和,得到 ri=(m+∑jaijyj)/2r_i=(m+\sum_j a_{ij}y_j)/2,移项可得 2(ri−xi)=m−2xi+∑jaijyj2(r_i-x_i)=m-2x_i+\sum_j a_{ij}y_j。所以常数项 c[i] 必须是 m-2*x[i],选择 C。

  3. 进一步理解整段算法:利用 ∣z∣=max⁡s∈{−1,1}sz|z|=\max_{s\in\{-1,1\}}sz,为每个学生枚举一个符号 sis_i,就能把绝对值展开。两倍目标值的最大值可写为 max⁡smax⁡y(∑isici+∑jyj∑isiaij)\max_s\max_y(\sum_i s_i c_i+\sum_j y_j\sum_i s_i a_{ij})。

  4. 固定 s 后,记 C=∑isiciC=\sum_i s_i c_i、qj=∑isiaijq_j=\sum_i s_i a_{ij}。每道题独立选择 y,使 yjqj=∣qj∣y_jq_j=|q_j|,于是当前符号方案的最优值是 C+∑j∣qj∣C+\sum_j|q_j|,这就解释了程序中的 C、q 和 S。

  5. 算法枚举的是 n 名学生的符号,共 2n2^n 种,而不是枚举 m 道题的 2m2^m 份标准答案;最终还原达到最大值的一份答案即可。

第 40 题(单选题,3 分)

②处应填( )。

A.
mask | (mask >> 1)
B.
mask ^ (mask >> 1)
C.
mask & (mask >> 1)
D.
mask ^ ((mask >> 1) + 1)

正确答案B

解析详情
答案 B格雷码枚举
思路与推导
  1. 上一空的推导表明,需要枚举 n 个学生的符号组合。程序用二进制位 0 表示 s[i]=-1、位 1 表示 s[i]=1,并希望相邻组合只翻转一个学生,以便增量维护 C、q 和 S。

  2. 二进制反射格雷码的生成公式是 g=mask^(mask>>1),所以选择 B。以 3 位为例,mask 从 0 到 7 时,g 依次为 000、001、011、010、110、111、101、100,每次只变化一位,且覆盖全部 8 个状态。

  3. 普通二进制计数从 011 到 100 会同时翻转三位,不能用后面“只更新一个 k”的代码处理;格雷码正好保证这个前提。

  4. 每次翻转只需遍历 m 道题更新一名学生的贡献,枚举部分耗时 O(2nm)O(2^n m),而不是每个状态都重新遍历全部 n 名学生。

第 41 题(单选题,3 分)

③处应填( )。

A.
__builtin_ctzll(d) + 1
B.
__builtin_popcountll(d)
C.
__builtin_ctzll(g)
D.
__builtin_ctzll(d)

正确答案D

解析详情
答案 D定位翻转位
思路与推导
  1. lst 保存前一个格雷码,g 是当前格雷码。按位异或 d=g^lst 会把未变化的位变成 0,把发生变化的位变成 1。

  2. 相邻格雷码恰好只变一位,所以 d 必定是某个 2k2^k,且不为 0。例如从 001 变为 011,d=010,变化的是从 0 开始编号的第 1 位。

  3. __builtin_ctzll(d) 返回 d 末尾连续 0 的数量。对于 d=2kd=2^k,恰好有 k 个末尾 0,因此它直接给出变化学生的数组下标 k,选择 D。

  4. 循环从 mask=1 开始,与初始 lst=0 的差也非零,不会调用 ctzll(0)。popcountll(d) 则始终是 1,只能说明变化了一位,无法确定具体位置。

第 42 题(单选题,3 分)

④处应填( )。

A.
2ll * s[k] * c[k]
B.
s[k] * c[k]
C.
2ll * (s[k] - c[k])
D.
2ll * c[k]

正确答案A

解析详情
答案 A增量更新
思路与推导
  1. C 的含义是 ∑isici\sum_i s_i c_i。每次格雷码只改变第 k 个学生,其他项不变,只需更新这一项。

  2. 注意此行执行时 s[k] 还是旧符号。旧贡献为 s[k]*c[k],翻转后的新贡献为 -s[k]*c[k],所以“新减旧”为 -2*s[k]*c[k]。

  3. 代码写成 C-=④,因此④应填 2ll*s[k]*c[k],选择 A。例如 c[k]=3、旧 s[k]=-1 时,贡献从 -3 变成 +3,C 应增加 6;执行 C-=2*(-1)*3 正好如此。

  4. 下面 q[j]-=2ll*s[k]*v 采用完全相同的更新逻辑,处理完所有贡献后才执行 s[k]=-s[k]。若提前翻转符号,更新方向就会反过来。

第 43 题(单选题,3 分)

⑤处应填( )。

A.
v >= (n & 1)
B.
v > (n & 1)
C.
v + (n & 1) >= 0
D.
v * (n & 1) >= 0

正确答案A

解析详情
答案 A符号选择与奇偶性
思路与推导
  1. 程序先从 best 还原最优的学生符号 s,再计算当前列 v=∑isiaijv=\sum_i s_i a_{ij}。标准答案选 A 时,该列贡献为 v;选 B 时,该列贡献为 -v。因此 v>0 应选 A,v<0 应选 B,v=0 时两种选择一样好。

  2. 由于每一项 siaijs_i a_{ij} 都是 +1 或 -1,v 是 n 个奇数的和,所以 v 与 n 同奇偶。

  3. 若 n 为偶数,n&1=0,条件 v>=(n&1) 就是 v>=0,把平局归给 A;若 n 为奇数,n&1=1,v 不可能为 0,v>=1 恰好等价于 v>0。故选 A。

  4. 严格比较 v>(n&1) 在 n 为奇数且 v=1 时会错选 B;例如只有一名学生、该列正贡献为 1 时,应该选 A 才能得到更大值。条件 v+(n&1)>=0 则会在奇数 n、v=-1 时反向出错。