试卷解析 · 理解每一步
洛谷 SCP-S 2026 第一轮模拟赛(提高级 C++)
CSPS-2026-R1-Mock-Luogu
试卷解析总览,可直接查看每题答案与解析。
第 1 题(单选题,2 分)
在 Linux 系统下本地测试函数式交互题,源程序名为 `scp.cpp`,源程序内引用了头文件 `scp.h`,题目下发交互库为 `grader.cpp`,则以下哪条编译命令可以输出正确的可执行文件 `scp`?( )
正确答案B
解析详情
`grader.cpp` 和 `scp.cpp` 都是需要编译并链接的源文件;`scp.h` 已由源程序包含,不应代替 `scp.cpp` 参与编译。参数 `-o scp` 指定输出可执行文件名,因此应选 B。
第 2 题(单选题,2 分)
关于最短路算法,以下说法中完全正确的是。( )
正确答案C
解析详情
标准 Dijkstra 在顶点出队后固定其最短距离,这一步依赖边权非负;存在负权边时,已固定的距离可能后来被进一步缩短。SPFA 最坏为 ,而 Floyd 还可通过 判断负环。
第 3 题(单选题,2 分)
某算法的时间复杂度的递归式为 ,则其渐进时间复杂度为?( )
正确答案D
解析详情
令 、,则 ,原式化为 。由主定理得 ,故 。
第 4 题(单选题,2 分)
使用数 1, 2, 3, 4, 5 各一个以及 2 个加号、2 个减号可组成的后缀表达式的值最大为?( )
正确答案D
解析详情
两个减号无论怎样嵌套,最终至少有一个数的系数为负,因此最大值不超过 。表达式 恰用两个加号和两个减号,其后缀式为 `5 4 + 3 + 1 2 - -`。
第 5 题(单选题,2 分)
运行如下 C++ 代码片段后,关于数组 `v` 的状态,下述说法正确的是?( )
std::vector<int> v = {9, 2, 7, 4, 5, 8, 1, 3, 6};
std::nth_element(v.begin(), v.begin() + 3, v.end());正确答案B
解析详情
`v.begin()+3` 指向按升序排列后的第 4 个元素,所以调用后 `v[3]=4`。其左侧三个位置都不大于 4,因元素互异,恰好包含 ,但三者内部次序没有保证。
第 6 题(单选题,2 分)
使用 `std::set` 维护有序集合时,设集合 中现有个元素,则使用 `std::lower_bound(s.begin(), s.end(), x)` 查找第一个 的元素的时间复杂度为?( )
正确答案C
解析详情
通用 `std::lower_bound` 在 `set` 的双向迭代器上虽只需 次比较,但移动迭代器到中点累计需要 次递增。因此总时间为 ;成员函数 `s.lower_bound(x)` 才是 。
第 7 题(单选题,2 分)
以下选项中,哪个序列不可能是对长度为 6 的字符串 运行 KMP 算法得到的 `next` 数组(定义 `next[i]` 为 的最长相等真前后缀长度)?( )
正确答案C
解析详情
序列 C 中 `next[2]=1,next[3]=0,next[4]=1,next[5]=2` 迫使前五个字符形如 `aabaa`。若 `next[6]=1`,第 6 个字符必须为 `a`,此时末尾 `aa` 又与开头 `aa` 相同,故 `next[6]` 至少为 2,产生矛盾。
第 8 题(单选题,2 分)
对 10 个互不相同且均在 之间的整数建立深度为 5(定义为叶子到根的距离)的 01-Trie,所可能得到的最多结点数和最少结点数之差为?( )
正确答案B
解析详情
结点最多时,各层不同前缀数为 ,连同根共 个。结点最少时尽量共享前缀,各层至少为 ,连同根共 个,差为 。
第 9 题(单选题,2 分)
使用数据结构维护序列区间信息 ,其中 为一般二元运算,下列说法正确的是?( )
正确答案C
解析详情
树状数组用前缀信息求区间 时,需要从前缀 中消去前缀 ,因此运算必须存在逆元。线段树合并要求结合律而非交换律;普通 ST 表利用重叠还要求幂等性。
第 10 题(单选题,2 分)
对于一个包含 10 个顶点的无向简单图 ,设其补图为 ,下列说法正确的是?( )
正确答案D
解析详情
对任一顶点 ,有 。若 与补图都拥有欧拉回路,则该顶点在两图中的度都应为偶数,其和也应为偶数,与 9 为奇数矛盾。
第 11 题(单选题,2 分)
计算矩阵 每项对 3 取模的值为?( )
正确答案D
解析详情
设 ,则 。Fibonacci 数列模 3 的周期为 8,而 ,故结果为 。
第 12 题(单选题,2 分)
有一个正十二面体,共 20 个顶点、30 条棱,每个面都是正五边形,其棱长为 1,顶点之间只能通过棱互相到达,则所有 个点对间最短路长度之和为?( )
正确答案B
解析详情
从任一顶点出发,到距离 的顶点数依次为 ,距离和为 。对 20 个起点求和后每个无序点对被计算两次,故总和为 。
第 13 题(单选题,2 分)
有一排 10 盏灯,初始时全部为关闭状态;每次操作可以选定一个连续区间,并将该区间内的灯亮灭状态反转,则恰好 3 次操作后,这 10 盏灯可能呈现出多少种不同的亮灭状态?( )
正确答案C
解析详情
用 11 个边界位表示灯串差分,一次区间反转恰翻转两个边界位。三次操作后边界向量的非零位数只能为 ,这些情况都可通过拆分区间或重复操作实现,故状态数为 。
第 14 题(单选题,2 分)
对于长度为 2026 的排列 ,初始时对于所有位置 均有;现在进行恰好 次操作,每次操作可以交换相邻两个位置上的数,则操作完成后得到的新排列 的最长上升子序列长度最短是多少?( )
正确答案D
解析详情
若最长上升子序列至多为 ,最少逆序数由 个尽量等大的下降块取得。 时最少为 ; 时可构造 187 个下降块并微调为 9976 个逆序,再用 24 次往返交换补足操作数。故最短为 187。
第 15 题(单选题,2 分)
对于如下图所示的无向图 ,在所有将边赋予 中互不相同的整数边权的方案中, 的最小生成树的权值和之和为?( )

正确答案B
解析详情
图中 7 条边的权值是 到 的一个排列,共 种。按 Kruskal 枚举,生成树权值为 的方案数分别是 ,故总和为 。
二、阅读程序(1)
(1)
1 | #include <bits/stdc++.h>
2 | using namespace std;
3 |
4 | const int N = 20, mod = 998244353;
5 | int n, a[N][N], f[1 << N];
6 |
7 | int main() {
8 | cin >> n;
9 | for (int i = 0; i < n; i++)
10 | for (int j = 0; j < n; j++)
11 | cin >> a[i][j];
12 | f[0] = 1;
13 | for (int i = 0; i < (1 << n); i++)
14 | for (int j = 0; j < n; j++)
15 | if (i >> j & 1)
16 | (f[i] += 1ll * f[i ^ (1 << j)] *
17 | a[__builtin_popcount(i) - 1][j] % mod) %= mod;
18 | cout << f[(1 << n) - 1];
19 | return 0;
20 | }保证输入的整数满足 ,,完成下面的判断题和单选题。
第 16 题(判断题,1 分)
当 时,程序不会出现数组越界访问。( )
正确答案正确
解析详情
当 时,`a` 只访问下标 `0` 到 `19`,`f` 的最大下标为 `(1 << 20) - 1`,均在声明范围内。第 17 行仅在掩码含第 `j` 位时执行,此时 `popcount(i)-1` 也不会为负。
第 17 题(判断题,1.5 分)
只要输入符合限定要求,无论输入的是什么,输出都一定是正整数。( )
正确答案错误
解析详情
输出是所有排列乘积之和对 取模,正数取模后可能为 0。例如 、矩阵为 时,结果为 。
第 18 题(判断题,1.5 分)
删除第 16 行的 `1ll *`,不会对程序的输出产生任何影响。( )
正确答案错误
解析详情
`f[...]` 与 `a[...]` 都可能接近 ,乘积约为 ,超过 32 位 `int` 范围。`1ll *` 使乘法在 `long long` 中完成;删除后会先发生有符号整数溢出,输出可能改变。
第 19 题(单选题,3 分)
当输入为 `3 1 2 3 2 3 1 3 1 2` 时,输出为( )?
正确答案C
解析详情
程序计算矩阵 permanent,即对 6 个列排列累加对应乘积。该输入的 6 项为 ,总和为 ,因此输出 54。
第 20 题(单选题,3 分)
该代码的时间复杂度为( )?
正确答案C
解析详情
外层枚举全部 个掩码,内层对每个掩码固定枚举 个列位置;位判断和转移均为常数时间。因此总时间复杂度为 。
第 21 题(单选题,3 分)
当 时,代码第 15 行的 `if` 语句中,表达式值为真的次数是( )?
正确答案A
解析详情
对固定的第 `j` 位,在 个掩码中恰有一半即 个掩码含该位。共有 14 个 `j`,所以 `if` 条件为真的总次数为 。
二、阅读程序(2)
(2)
1 | #include <bits/stdc++.h>
2 | using namespace std;
3 |
4 | typedef long long ll;
5 | ll len[90], dp[90][2];
6 |
7 | void init() {
8 | len[0] = 1, len[1] = 2, dp[1][1] = 1;
9 | for (int i = 2; i <= 86; i++) {
10 | len[i] = len[i - 1] + len[i - 2];
11 | int op = len[i - 1] & 1;
12 | dp[i][0] = dp[i - 1][0] + dp[i - 2][0 ^ op];
13 | dp[i][1] = dp[i - 1][1] + dp[i - 2][1 ^ op];
14 | }
15 | }
16 |
17 | int solve1(int n, int p) {
18 | string s = "0", nxt = "01", tmp;
19 | while (nxt.length() < n)
20 | tmp = nxt + s, s = nxt, nxt = tmp;
21 | int ans = 0;
22 | for (int i = p; i < n; i += 2) ans += nxt[i] - '0';
23 | return ans;
24 | }
25 |
26 | int value(int pos) {
27 | if (pos <= 1) return pos;
28 | int k = 0;
29 | while (len[k + 1] <= pos) k++;
30 | return value(pos - len[k]);
31 | }
32 |
33 | int solve2(int n, int p) {
34 | int ans = 0;
35 | for (int i = p; i < n; i += 2) ans += value(i);
36 | return ans;
37 | }
38 |
39 | ll solve3(ll n, int p) {
40 | int k = 0; ll ans = 0;
41 | while (len[k + 1] <= n) k++;
42 | for (; k >= 0; k--) if (len[k] <= n)
43 | ans += dp[k][p], n -= len[k], p ^= len[k] & 1;
44 | return ans;
45 | }
46 |
47 | int main() {
48 | ll n; int p;
49 | cin >> n >> p, init();
50 | if (n <= 1e6)
51 | cout << solve1(n, p) << ' ' << solve2(n, p) << ' '
52 | << solve3(n, p) << endl;
53 | else
54 | cout << solve3(n, p) << endl;
55 | return 0;
56 | }假设输入的整数满足 ,,完成下面的判断题和单选题。
第 22 题(判断题,1.5 分)
`solve1` 函数中使用朴素的字符串拼接,时间复杂度为 。( )
正确答案错误
解析详情
拼接出的字符串长度按 Fibonacci 数增长,历次拼接复制的字符总数是 。随后扫描约 个位置,因此 `solve1` 总时间为 ,不是 。
第 23 题(判断题,1.5 分)
将第 11 行的 `len[i - 1] & 1` 改为 `i % 3 < 2`,程序仍然能正常运行,且输出结果不变。( )
正确答案正确
解析详情
`len` 满足 Fibonacci 型递推,其奇偶性以“奇、偶、奇”为周期。对 ,`len[i-1]&1` 的序列与 `i%3<2` 都是“0,1,1”循环,因此 `op` 始终相同,`dp` 和输出不变。
第 24 题(判断题,1.5 分)
对于输入范围内的所有 ,`solve3(n, 0)` 与 `solve3(n, 1)` 的返回值之差的绝对值均不超过 1。( )
正确答案错误
解析详情
取 ,前 15 个字符为 `010010100100101`。偶数下标上的 1 有 4 个,奇数下标上的 1 有 2 个,所以 `solve3(15,0)` 与 `solve3(15,1)` 相差 2,已经超过 1。
第 25 题(单选题,3 分)
`solve2` 函数在最坏情况下的时间复杂度是?( )
正确答案D
解析详情
`solve2` 要处理 个位置。`value(pos)` 最坏递归 层,而每层的 `while` 又从 `len[1]` 起扫描 项,故单次最坏为 ,总计 。
第 26 题(单选题,3 分)
对于输入数据 `233 0`,输出结果的第一个数为?( )
正确答案B
解析详情
因 ,程序输出的第一个数是 `solve1(233,0)`。生成长度不少于 233 的 Fibonacci 字后,统计下标 上的字符 1,共得到 45 个,因此第一个数为 45。
第 27 题(单选题,3 分)
若某次调用 `solve3(n, p)` 时,语句 `ans += dp[k][p]` 恰好执行了 8 次,则输入的 最小可能是多少?( )
正确答案B
解析详情
第 42—43 行执行次数等于 的 Zeckendorf 分解项数。要有 8 项且使 最小,应取最小的 8 个互不相邻 Fibonacci 长度:,其和为 。
二、阅读程序(3)
(3)
1 | #include <bits/stdc++.h>
2 | using namespace std;
3 |
4 | typedef long long ll;
5 | typedef unsigned long long ull;
6 | const int N = 100005, M = 200005, S = 400037;
7 | const int P = 998244353, iv2 = (P + 1) / 2;
8 |
9 | int n, m, w[M], sum, dep[N], ct[M], sw[M], cur[M], ans[N];
10 | ull h[N], he[M];
11 | bool vis[N];
12 | vector<pair<int, int> > G[N], T[N];
13 | mt19937_64 rnd(random_device{}());
14 |
15 | int tot, hd[S];
16 | struct node { int nxt; ull key; } mp[M];
17 | int get(ull key) {
18 | int u = key % S;
19 | for (int i = hd[u]; i; i = mp[i].nxt)
20 | if (mp[i].key == key) return i;
21 | return mp[++tot] = {hd[u], key}, hd[u] = tot;
22 | }
23 |
24 | void dfs1(int u, int p) {
25 | vis[u] = 1;
26 | for (auto [v, i] : G[u]) if (i ^ p) {
27 | if (!vis[v])
28 | dep[v] = dep[u] + 1, T[u].push_back({v, i}),
29 | dfs1(v, i), h[u] ^= h[v], he[i] = h[v];
30 | else if (dep[v] < dep[u])
31 | he[i] = rnd(), h[u] ^= he[i], h[v] ^= he[i];
32 | }
33 | }
34 |
35 | void dfs2(int u, ll c, ll b, ll b2) {
36 | ans[u] = ((c + b * sum - (b * b + b2) % P * iv2) % P + P) % P;
37 | for (auto [v, i] : T[u]) {
38 | ll w = ::w[i];
39 | if (!he[i]) {
40 | dfs2(v, c, (b + w) % P, (b2 + w * w) % P);
41 | continue;
42 | }
43 | int j = get(he[i]);
44 | if (ct[j] == 1) dfs2(v, c, b, b2);
45 | else {
46 | int d = (w * (sw[j] - cur[j] * 2 - w) % P + P) % P;
47 | cur[j] = (cur[j] + w) % P;
48 | dfs2(v, (c + d) % P, b, b2);
49 | cur[j] = (cur[j] - w + P) % P;
50 | }
51 | }
52 | }
53 |
54 | int main() {
55 | ios::sync_with_stdio(0), cin.tie(0);
56 | cin >> n >> m;
57 | for (int i = 1, u, v; i <= m; i++)
58 | cin >> u >> v >> w[i], sum = (sum + w[i]) % P,
59 | G[u].push_back({v, i}), G[v].push_back({u, i});
60 | dfs1(1, 0);
61 | for (int i = 1, j; i <= m; i++) if (he[i])
62 | j = get(he[i]), ct[j]++, sw[j] = (sw[j] + w[i]) % P;
63 | dfs2(1, 0, 0, 0);
64 | for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
65 | }假设输入的整数满足 ,,,且输入的图连通、无自环或重边,不考虑异或哈希值的冲突,完成下面的判断题和单选题。
第 28 题(判断题,1.5 分)
代码中使用了哈希表,攻击者可以在不获知随机数种子的前提下,通过构造数据,使得程序在该数据下的期望时间复杂度退化至平方级别。( )
正确答案错误
解析详情
哈希表的键 `he[i]` 由未知的 64 位随机数异或得到;不知道随机种子时,攻击者无法预先把大量键定向到同一个 `key%400037` 桶。桶分布在期望意义下均匀,所以不能通过固定输入把期望复杂度构造成平方级。
第 29 题(判断题,1.5 分)
若对于 有 `he[i]` 和 `he[j]` 相等且均不为 0,则图中存在一个环同时包含边。( )
正确答案正确
解析详情
每条返祖边产生一个随机标记,并把该标记异或到其基本环上的所有边。忽略哈希冲突时,`he[i]=he[j]≠0` 表示两边所属的基本环集合相同且非空;任选其中一个基本环,就得到同时包含边 的简单环。
第 30 题(判断题,1.5 分)
若 ,且对于 有,则输出的所有数之和(在对 取模意义下)等于。( )
正确答案正确
解析详情
该图是一条边权全为 1 的树,到顶点 时有 、、。故 ;令 从 0 到 求和,得到 。
第 31 题(单选题,3 分)
这份程序对于每个点 ,求得了( )的答案对 取模的结果。
正确答案C
解析详情
`b` 汇总 1 到 的 DFS 树路径上的割边,任取其中一条与另一条边配对,所有 1 到 的路径都至少经过前者。`c` 再统计同一非零 `he` 类中构成二边割集的边对;删去这两边会隔断 1 与 。因此统计条件是“每条路径至少含二者之一”,即 C。
第 32 题(单选题,3 分)
在 `dfs2` 函数中,若执行了分支 `if (ct[j] == 1) dfs2(v, c, b, b2)`,设当前正沿树边 向下遍历,则 在原图中满足?( )
正确答案D
解析详情
`he[i]≠0` 说明当前树边 位于环上,本身不是割边;`ct[j]==1` 又说明没有另一条边与它组成相同的二边割类。因而删除 后图仍连通,且不会有某条环边因只剩唯一通路而新变成割边。
第 33 题(单选题,3 分)
对于如下输入数据,输出的第 8 个数为?( )
10 10
1 2 10
2 3 20
3 4 30
4 5 40
5 6 10
6 7 10
7 8 10
8 9 10
9 10 10
10 5 10正确答案C
解析详情
到顶点 8 的四条割边权为 ,故 ,基础项为 。六条权为 10 的环边同属一类,走到 8 前三次增量依次为 ,使 ,故 。
三、完善程序(1)
(1)(线性筛)给定一个正整数 ,要对于 求出取模 998244353 的结果。满足。你需要设计一个 的算法。
提示:在线性筛的过程中同步计算 。质数可以直接使用快速幂;合数被筛到时,尝试将 表示成与 的幂的乘积,从而由已有结果完成转移。
令 。的幂可将指数按 分块并预处理;的幂利用对于枚举的,指数递增的性质,根据相邻质数之差增量维护。总时间复杂度即可达到 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5, SN = (int)sqrt(N) + 5, mod = 998244353;
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1ull * res * a % mod;
①
b >>= 1;
}
return res;
}
int bsgs1[SN][SN], bsgs2[SN][SN];
bool vis[N];
int f[N], pr[N / 10], len;
int powers[N], S;
int main() {
int n;
scanf("%d", &n);
f[1] = 1;
const int B = sqrt(n);
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
pr[++len] = i, f[i] = qpow(i, i);
if (i <= B) {
bsgs1[i][0] = 1;
for (int j = 1; j <= B; j++)
bsgs1[i][j] = 1ull * bsgs1[i][j - 1] * f[i] % mod;
bsgs2[i][0] = 1;
for (int j = 1; j <= B; j++)
②
}
}
powers[0] = 1;
int cur = 1, gap = 0;
for (int j = 1; j <= len && i * pr[j] <= n; j++) {
vis[pr[j] * i] = 1;
int now = pr[j] - pr[j - 1];
③
powers[ex] = 1ull * powers[ex - 1] * f[i] % mod;
gap = max(gap, now);
cur = 1ull * cur * powers[now] % mod;
④
if (⑤)
break;
}
}
for (int i = 1; i <= n; i++)
printf("%d ", f[i]);
return 0;
}第 34 题(单选题,3 分)
① 处应填( )。
a = 1ull * a * a % mod;a = 1ull * a * b % mod;res = 1ull * a * a % mod;a = 1ull * res * res % mod;正确答案A
解析详情
每轮处理完指数最低位后,指数执行 `b>>=1`,底数必须同步平方,才能让下一位对应 。因此应填写 `a = 1ull * a * a % mod;`。
第 35 题(单选题,3 分)
② 处应填( )。
bsgs2[i][j] = 1ull * bsgs2[i][j - 1] * qpow(i, B) % mod;bsgs2[i][j] = 1ull * bsgs1[i][j - 1] * bsgs2[i][B - 1] % mod;bsgs2[i][j] = 1ull * bsgs1[i][j - 1] * qpow(f[i], B) % mod;bsgs2[i][j] = 1ull * bsgs2[i][j - 1] * bsgs1[i][B] % mod;正确答案D
解析详情
`bsgs1[i][j]=f[i]^j`,为了按 分块表示指数,还需令 `bsgs2[i][j]=f[i]^{jB}`。由相邻块递推即乘上 `bsgs1[i][B]=f[i]^B`,所以填写 `bsgs2[i][j - 1] * bsgs1[i][B]`。
第 36 题(单选题,3 分)
③ 处应填( )。
for (int ex = 1; ex <= now; ex++)for (int ex = gap + 1; ex <= now; ex++)for (int ex = 1; ex <= now; ex += B)for (int ex = gap + 1; ex <= now; ex += B)正确答案B
解析详情
`powers[ex]` 保存当前 `f[i]^ex`,`gap` 表示此前已经计算到的最大指数。当前质数差为 `now` 时,只需从 `gap+1` 补算到 `now`;若 `now<=gap`,循环自然不执行,因此应选 B。
第 37 题(单选题,3 分)
④ 处应填( )。
f[pr[j] * i] = 1ull * bsgs2[pr[j] * i][i % B] * bsgs1[pr[j] * i][i / B] % mod * cur % mod;f[pr[j] * i] = 1ull * bsgs1[pr[j]][i % B] * bsgs2[pr[j]][i / B] % mod * cur % mod;f[pr[j] * i] = 1ull * bsgs2[pr[j]][i % B] * bsgs1[pr[j]][i / B] % mod * cur % mod;f[pr[j] * i] = 1ull * bsgs1[pr[j] * i][i % B] * bsgs2[pr[j] * i][i / B] % mod * cur % mod;正确答案B
解析详情
对 ,有 。`cur` 由相邻质数差增量维护为 ;而 按 拆成 `bsgs1[p][i%B] * bsgs2[p][i/B]`,故选 B。
第 38 题(单选题,3 分)
⑤ 处应填( )。
pr[j] > i!(pr[j] % i)!(i % pr[j])i > pr[j]正确答案C
解析详情
线性筛要求每个合数只由其最小质因子筛到。当枚举质数 `pr[j]` 已整除 `i` 时,继续枚举会让后续合数重复生成,因此条件应为 `!(i % pr[j])`。
三、完善程序(2)
(2)(静态 Top Tree)
给定一棵由主链和若干叶子组成的带权树。主链包含顶点 ,对于 ,顶点 与顶点之间有一条长度为的边。每个主链顶点还可能连接任意多条叶边;所有叶边按照输入顺序编号为。共有 次操作,每次操作为以下三种之一:
- `1 x y`:将主链边 的长度修改为; - `2 x y`:将编号为 的叶边长度修改为; - `3 l r`:询问由主链顶点 以及与它们相连的所有叶子组成的子树的直径长度。
其中,,,,所有边长均为不超过 的非负整数。输入保证所有操作均合法。
输入的第一行包含三个整数 。第二行包含 个整数。接下来的 行中,第行首先包含整数,随后包含 个整数,依次表示与顶点相连的叶边长度;保证。最后 行每行包含一次操作。
为解决该问题,可以建立一棵静态 Top Tree。Top Tree 中的每个结点表示原树的一个连通子图,称为簇。一个顶点若属于该簇,同时还与不属于该簇的树边相连,则称为边界顶点。每个簇至多有两个边界。
静态 Top Tree 使用 rake(R)和 compress(C)两种合并。对于簇 ,记 为簇内边的集合,为边界顶点集合。边集不相交的簇只有在恰有一个公共边界,即时才能合并,以保证结果仍为簇。rake 要求只有一个边界,compress 要求均有两个边界。若分别为两种合并的结果,则,,。
Top Tree 上每个顶点代表的簇都是两个儿子顶点的簇以 R 或 C 方式合并得到。在本题中,算法先平衡合并每个主链顶点的叶簇,再将分支簇挂到对应主链边的左端,并在线段树中合并主链边簇。这样建立的 Top Tree 就可以在树上取出一个区间的簇回答询问。实现上,令簇信息 `Node` 维护边界距离 `w`、从左右边界出发的最远距离 `l` 和 `r`、簇内直径 `d` 及边界个数 `c`,即可实现合并。
以上算法的预处理复杂度为 ;每次操作复杂度为 。
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 100005, M = 200005;
int n, k, m, z, tot, st[N], bel[N], id[N], lc[M], rc[M], fa[M];
long long a[N];
struct Node {
long long w, l, r, d;
int c;
} f[M], t[N << 2];
Node leaf(long long x) { return {0, x, x, x, 1}; }
Node path(long long x) { return {x, x, x, x, 2}; }
Node merge(Node a, Node b, char o, int s = 0) {
Node z = a;
if (o == 'R') {
if (a.c == 1) {
z.l = z.r = max(a.l, b.l);
} else if (s == 0) {
z.l = max(a.l, b.l); z.r = max(a.r, a.w + b.l);
} else {
z.l = max(a.l, a.w + b.l); z.r = max(a.r, b.l);
}
z.d = max(max(a.d, b.d), ①);
} else {
z.w = a.w + b.w; z.l = max(a.l, a.w + b.l);
z.r = max(b.r, b.w + a.r);
z.d = max(max(a.d, b.d), a.r + b.l); z.c = 2;
}
return z;
}
int join(int x, int y) {
int u = ++tot; lc[u] = x; rc[u] = y; fa[x] = fa[y] = u;
f[u] = merge(f[x], f[y], 'R'); return u;
}
int build_rake(int l, int r) {
if (l == r) return l;
int mid = (l + r) >> 1;
return join(build_rake(l, mid), build_rake(mid + 1, r));
}
Node star(int x) { return st[x] ? f[st[x]] : leaf(0); }
Node make(int x) {
Node y = path(a[x]);
if (st[x]) ②;
return y;
}
void build(int x, int l, int r) {
if (l == r) { t[x] = make(l); return; }
int mid = (l + r) >> 1;
build(x << 1, l, mid); build(x << 1 | 1, mid + 1, r);
t[x] = merge(t[x << 1], t[x << 1 | 1], 'C');
}
void change(int x, int l, int r, int q) {
if (l == r) { t[x] = make(l); return; }
int mid = (l + r) >> 1;
if (q <= mid) change(x << 1, l, mid, q);
else change(x << 1 | 1, mid + 1, r, q);
t[x] = merge(t[x << 1], t[x << 1 | 1], 'C');
}
Node query(int x, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return t[x];
int mid = (l + r) >> 1;
if (qr <= mid) return query(x << 1, l, mid, ql, qr);
if (ql > mid) return query(x << 1 | 1, mid + 1, r, ql, qr);
Node p = query(x << 1, l, mid, ql, qr);
Node q = query(x << 1 | 1, mid + 1, r, ql, qr);
return ③;
}
void modify(int x, long long y) {
int u = id[x]; f[u] = leaf(y);
while (fa[u]) { u = fa[u]; f[u] = merge(f[lc[u]], f[rc[u]], 'R'); }
if (④) change(1, 1, n - 1, bel[x]);
}
int main() {
cin >> n >> k >> m;
for (int i = 1; i < n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
int c, l = tot + 1; cin >> c;
while (c--) {
long long x; cin >> x; bel[++z] = i; id[z] = ++tot;
f[tot] = leaf(x);
}
if (l <= tot) st[i] = build_rake(l, tot);
}
build(1, 1, n - 1);
while (m--) {
int o, x, y; cin >> o >> x >> y;
if (o == 1) { a[x] = y; change(1, 1, n - 1, x); }
else if (o == 2) modify(x, y);
else {
Node ans = ⑤;
if (x < y) ans = merge(ans, star(y), 'R', 1);
cout << ans.d << endl;
}
}
return 0;
}第 39 题(单选题,3 分)
① 处应填( )。
(a.c == 1 || s == 1 ? a.l : a.r) + b.l(a.c == 1 ? max(a.d, b.d) : (s == 0 ? a.l : a.r)) + b.l(a.c == 1 || s == 0 ? a.l : a.w) + b.l(a.c == 1 || s == 0 ? a.l : a.r) + b.l正确答案D
解析详情
簇 `b` 只有一个边界,跨越两簇的最长路径等于 `b.l` 加上簇 `a` 从公共边界出发的最远距离。`a.c==1` 或 `s==0` 时公共边界对应 `a.l`,否则对应 `a.r`,故交叉项为 `(a.c == 1 || s == 0 ? a.l : a.r) + b.l`。
第 40 题(单选题,3 分)
② 处应填( )。
y = merge(y, star(x), 'R', 0);y = merge(y, star(x), 'R', 1);y = merge(star(x), y, 'R', 0);y = merge(y, star(x), 'C');正确答案A
解析详情
`path(a[x])` 表示主链边 ,`star(x)` 是挂在左端点 的叶簇。应保持主链簇 `y` 为第一个参数,并以 `s=0` 在左边界执行 rake,所以是 `y = merge(y, star(x), 'R', 0);`。
第 41 题(单选题,3 分)
③ 处应填( )。
merge(q, p, 'C')merge(p, q, 'R', 0)merge(p, q, 'R', 1)merge(p, q, 'C')正确答案D
解析详情
线段树左侧查询结果 `p` 与右侧结果 `q` 是相邻的双边界主链簇,应按从左到右的顺序做 compress。故返回 `merge(p, q, 'C')`,这样新簇的左右边界和距离方向保持一致。
第 42 题(单选题,3 分)
④ 处应填( )。
bel[x] < nx < nbel[x] <= nst[bel[x]] != 0正确答案A
解析详情
顶点 `bel[x]` 的叶簇只在 `make(bel[x])` 中并入其右侧主链边,而线段树只建立了边 到 。因此仅当 `bel[x]<n` 时需要调用 `change`;顶点 的星簇在查询末端另行合并。
第 43 题(单选题,3 分)
⑤ 处应填( )。
x == y ? leaf(0) : query(1, 1, n - 1, x, y - 1)x == y ? star(x) : query(1, 1, n - 1, x, y)x == y ? star(x) : query(1, 1, n - 1, x, y - 1)x == y ? star(x) : query(1, 1, n - 1, x + 1, y - 1)正确答案C
解析详情
查询主链顶点区间 时,内部主链边恰为 ,所以 时查询 `[x,y-1]`,随后再挂上 `star(y)`。当 时没有主链边,答案簇就是 `star(x)`,故选 C。