CSP-S 2024 初赛解析

news/2024/9/22 1:46:49/文章来源:https://www.cnblogs.com/stelayuri/p/18424749

时间紧任务重,可能有误,烦请指正 QwQ

题目内代码可能有些许错误,应该不大影响查看吧,这个难改就不改哩


第1题 (2分)

在Linux系统中,如果你想显示当前工作目录的路径,应该使用哪个命令?( )

A. pwd

B. cd

C. ls

D. echo

pwd 可以显示当前的工作路径

cd 表示切换工作路径

ls 表示列出当前工作路径下的所有文件和目录

echo 表示将输入的字符串进行标准输出

第2题 (2分)

假设一个长度为n 的整数数组中每个元素值互不相同,且这个数组是无序的。要找到这个数组中最大元素的时间复杂度是多少?( )

A. O(n)

B. O(log⁡n)

C. O(nlog⁡n)

D. O(1)

无序数组找最大值

打擂台,\(O(n)\)

第3题 (2分)

在 C++ 中,以下哪个函数调用会造成栈溢出?( )

A. int foo() { return 0; }

B. int bar() { int x = 1; return x; }

C. void baz() { int a[1000]; baz(); }

D. void qux() { return ; }

明显 C 选项递归没有设置边界,并且每一层递归都开了一个无用的数组,会造成栈溢出

第4题 (2分)

在一场比赛中,有 10 名选手参加,前三名将获得金、银、铜牌。若不允许并列、且每名选手只能获得一枚奖牌,则不同的颁奖方式共有多少种?( )

A. 120

B. 720

C. 504

D. 1000

相当于从 10 名选手中选出三名,然后分配金银铜牌

选出顺序不同所表示的方案也是不同的,所以求的是排列数

\(A_{10}^3 = 720\)

第5题 (2分)

下面哪个数据结构最适合实现先进先出(FIFO)的功能?( )

A. 栈

B. 队列

C. 线性表

D. 二叉搜索树

先进先出为队列

第6题 (2分)

已知 f(1)=1,且对于 n >= 2 有 f(n) = f(n - 1) + f(⌊n/2⌋),则 f(4) 的值为( )

A. 4

B. 5

C. 6

D. 7

发现在 \(n \ge 2\) 时,递推式只跟下标比自己小的项有关系,因此顺序递推即可

\(f(1) = 1\)

\(f(2) = f(1) + f(1) = 2\)

\(f(3) = f(2) + f(1) = 3\)

\(f(4) = f(3) + f(2) = 5\)

第7题 (2分)

假设有一个包含 n 个顶点的无向图,且该图是欧拉图,以下关于该图的描述中哪一项不一定正确?

A. 所有顶点的度数均为偶数

B. 该图连通

C. 该图存在一个欧拉回路

D. 该图的边数是奇数

首先发现 AB 两个选项同时成立时,与 C 选项是等价的

欧拉图就是拥有欧拉回路的图

只拥有欧拉通路,不拥有欧拉回路的图叫做半欧拉图

所以 ABC 三个选项必选

至于 D 选项,造一个 4 个点 4 条边组成的环,明显可以排除

第8题 (2分)

对数组进行二分查找的过程中,以下哪个条件必须满足?( )

A. 数组必须是有序的

B. 数组必须是无序的

C. 数组长度必须是2的幂

D. 数组中的元素必须是整数

因为二分查找要根据中间项来确定答案在哪一边

所以二分查找的前提条件是数组必须有序

第9题 (2分)

考虑一个自然数 n 以及一个模数 m。你需要计算 n 的逆元(即 n 在模 m 意义下的乘法逆元)。下列哪种算法最为适合?( )

A. 使用暴力法依次尝试

B. 使用扩展欧几里得算法

C. 使用快速幂法

D. 使用线性筛法

如果题目中有说明 \(m\) 是质数,那么可以根据费马小定理推出 \(n^{-1} \equiv n^{m-2}(\bmod m)\),这时候是可以借助快速幂算法求解的

但题目并没有给定这个限制,因此费马小定理不能直接套用,只能借助扩展欧几里得求解线性同余方程来求逆元

扩展欧几里得还可以用于判断逆元是否存在,即 \(n, m\) 互质时才存在逆元

第10题 (2分)

在设计一个哈希表时,为了减少冲突,需要使用适当的哈希函数和冲突解决策略。已知某哈希表中有 n 个键值对,装的装载因子为 α (0 < α <= 1)。在使用开放地址法解决冲 突的过程中,最坏情况下查找一个元素的时间复杂度为( )。

A. O(1)
B. O(log⁡n)
C. O(1/(1−α))
D. O(n)

开放地址法也可以当作线性探测法

装载因子表示表中元素个数与表长度的比值,在 \((0, 1]\) 之间说明一定存在元素,且每个元素一定能找到对应的存入位置

想查找一个元素,最坏情况下就是表被全部占满了,并且在存入时每个元素都被哈希到了同一个位置上,然后按顺序往后线性探测,一个个存进后面第一个空位上

然后在查找时,每次想找一个数字,就得像插入过程一样,从前往后一个一个找,所以最坏情况就是 \(O(n)\) 把整张哈希表找一遍才找到对应数字

第11题 (2分)

假设有一棵 h 层的完全二叉树,该树最多包含多少个结点?( )

A. 2^h-1

B. 2^(h+1)-1

C. 2^h

D. 2^(h+1)

\(h\) 层的完全二叉树包含的结点总数应当是 \(2^0 + 2^1 + 2^2 + \dots + 2^{h-1} = 2^h - 1\)

第12题 (2分)

设有一个 10 个顶点的完全图,每两个顶点之间都有一条边。有多少个长度为 4 的环?( )

A. 120

B. 210

C. 630

D. 5040

考虑先从 \(10\) 个点中选出 \(4\) 个点,求组合方案,即 \(C_{10}^4 = 210\)

对于任意四个点,需要考虑其圆排列总数,即 \((4-1)! = 6\),但因为是图论题,因此顺时针和逆时针当作相同的方案,因此排列数应该是 \(\dfrac{(4-1)!}2 = 3\)

或者直接列举,假设四个点是 ABCD,明显能组成的不同环排列有 ABCD, ACBD, ACDB 三种

最终答案即 \(210 \times 3 = 630\)

第13题 (2分)

对于一个整数 n ,定义 f(n) 为 n 的各位数字之和。问使 f(f(x)) = 10 的最小自然数 x 是多少?( )

A. 29

B. 199

C. 299

D. 399

满足 \(f(x) = 10\)\(x\) 从小到大有 \(19, 28, 37, 46, 55, \dots\)

考虑 \(19\),再套一层,也就是找 \(f(x) = 19\)\(x\),发现最小的 \(x\) 就是 \(199\)

(或者,反正这是一道选择题而不是填空题,直接从小往大一个个选项试一遍就行)

第14题 (2分)

设有一个长度为 n 的 01 字符串。其中有 k 个 1。每次操作可以交换相邻两个字符,在最坏情况下将这 k 个 1 移到字符串最右边所需要的交换次数是多少?( )

A. k

B. k*(k-1)/2

C. (n-k)*k

D. (2n-k-1)*k/2

相当于给 01 串进行一个排序,每次交换相邻两个字符,可以当作让逆序对数减少 1

所以交换次数就等于 01 串的逆序对数

为了让逆序对数最多,肯定是让 1 全在左,0 全在右

左边 \(k\) 个 1 和右边 \(n-k\) 个 0 组成的逆序对总数即 \(k \times (n-k)\)

第15题 (2分)

如图是一张包含 7 个顶点的有向图,如果要删除其中一些边,使得从节点 1 到节点 7 没有可行路径,且删除的边数最少,请问总共有多少种可行的删除边的集合?( )

img

A. 1

B. 2

C. 3

D. 4

明显最少删除的边数为 2

这题可以假设先删一条边,然后画出剩下的图中的强连通分量,然后考虑方案数,会更方便一些

删法分别为:

  • \(5-7, 6-7\)
  • \(4-6, 5-7\)
  • \(2-5, 4-6\)
  • \(1-2, 4-6\)

第16题 (11.5 分)

imgimg

首先发现 logic 函数有点麻烦,但内部全都是位运算,所以不妨针对每一位,先画出真值表

| x | y | x&y | x^y | ~x&y | (x^y)|(~x&y) | logic |
| ---- | ---- | ----- | ----- | ------ | -------------- | ------- |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 | 1 | 1 |
| 1 | 0 | 0 | 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 0 | 0 | 0 | 1 |

明显 logic 函数是按位或运算

然后考虑代码,generate 就是生成 \(b\) 个数字放在 \(c\) 数组的下标 \(0\sim b-1\) 的位置上,其中 c[i] = (a | i) % (b + 1)

recursion 函数就是快排模板,但是加了个深度限制 depth,也就是说这个快排如果到达指定深度,即使没排完也会结束

第16 - 1题(1.5 分)

当 1000 ≥ d ≥ b 时,输出的序列是有序的。

A.对

B.错

\(b \le d \le 1000\) 时,因为数字数量 \(b\) 比限制深度 \(d\) 要小,并且快排在最坏情况下其实也就是有多少数字就递归多少层(\(O(n^2)\) 出现的情况)

因此这个深度限制就跟没有一样

不论什么情况,快排都一定会执行结束,到最后所有数字都会有序

第16 - 2题(1.5 分)

当输入"5 5 1" 时,输出为 "1 1 5 5 5"。

A.对

B.错

首先根据数据生成五个数字,如果只看按位或,得到的数是 5 5 7 7 5

generate 函数中位运算完成后会对 \(b+1\) 取模,因此得到的数是 5 5 1 1 5

因为 \(d=1\),因此快排只执行一次,模拟一遍快排

5 5 1 1 5
i       j执行两个 while5 5 1 1 5
i       ji <= j 成立,交换,然后 i++, j--5 5 1 1 5
i   j执行两个 while5 5 1 1 5
i   ji <= j 成立,交换,然后 i++, j--5 1 1 5 5ij执行两个 while5 1 1 5 5j ii <= j 不成立,结束

发现最终答案是 5 1 1 5 5

第16 - 3题(1.5 分)

假设数组 c 长度无限制,该程序所实现的算法的时间复杂度是 O(b) 的。

A.对

B.错

时间复杂度主要还是取决于快排,但注意本题还有一个深度限制

如果深度限制 \(d\) 较小,那么时间复杂度应该是 \(O(b \times d)\)

如果深度限制 \(d\) 较大,最好情况下时间复杂度是 \(O(b \log b)\),最坏情况下就是 \(O(b^2)\)

第16 - 4题(3 分)

函数 int logic(int x, int y) 的功能是( )。

A.按位与

B.按位或

C.按位异或

D.以上都不是

根据上面的分析,logic 函数是按位或

第16 - 5题(4 分)

当输入为 "10 100 100" 时,输出的第 100 个数是( )。

A.91

B.94

C.95

D.98

首先考虑到 \(96 = 64 + 32 = 2^6 + 2^5\)

也就是说 \(96_{10} = 1100000_2\)

那么 \(95_{10} = 1011111_2\)

又因为 \(10_{10} = 1010_2\)

发现 \(95\) 的后五位都是 \(1\),所以 \(95\) 以下的所有数字都按位或上 \(10\) 之后得到的数字大小都不会超过 \(95\)

\(96, 97, 98, 99\) 这四个整数,按位或 \(10\) 之后一定都超过了 \(100\),因此对 \(b+1 = 101\) 取模后的结果就变小了

所以生成的数字最大值就是 \(95\)

而本题给定的数字中 \(b = d\),所以深度限制可以忽略,快排一定能够把所有数字都排完,因此第 \(100\) 个数就是生成过程中出现的最大的数字,即 \(95\)

第17题 (14 分)

imgimg

假设输入的 s 是包含 n 个字符的 01 串,完成下面的判断题和单选题:

本题先从 solve2 函数入手,观察到 \(i\) 循环从 \(0\)\(2^n-1\),也就是把所有位数为 \(n\) 的二进制都枚举了一遍,然后 \(j\) 循环从 \(0\)\(n\)i & (1 << j) 即判断 \(i\)\(2^j\) 这一位上是否为 \(1\),如果是 \(1\) 就执行 num = num * 2 + (s[j] - '0') 并且 cnt++

先单独看 num = num * 2 + (s[j] - '0') 这一句,相当于把 \(i\) 的二进制上为 \(1\) 的那些位置的字符单独取出,然后二进制数位拼接成一个新数字,再结合前面 \(i\) 循环的枚举,不难看出这里相当于是把字符串的每个子序列都单独提了出来,当作一个二进制数,然后转为十进制的 num 变量

cnt++ 则相当于是在统计当前取出的这个子序列的长度

cnt <= m 时,会把 num 加到 ans

所以不难发现,solve2 就是在把字符串中所有长度不超过 \(m\) 的子序列全部取出来,当作二进制数,转为十进制后再全部相加

然后看 solve 函数,有了上面的推导,我们不妨猜想这个函数和前面那个函数所求的内容应该有所关联

\(14\) 行的 k = (j << 1) | (s[i] - '0'),因为字符串是一个 01 串,所以这句话不妨转化为 k = j * 2 + (s[i] - '0'),发现也就是在 \(j\) 的基础上,把 s[i] 代表的数字拼到 \(j\) 的二进制后面

然后在 j != 0 || s[i] == '1' 时,执行了 dp[k] += dp[j],也就是避免了从 \(0\) 转移到 \(0\) 的情况,不难看出 dp[k] 应该是在计数,统计的是选出的子序列二进制为 \(k\) 的方案总数。因此在 \(j\) 的方案后面拼上一个 s[i] 变成 \(k\) 这个数字后,\(k\) 的总方案数就会加上 \(j\) 原本的方案数

然后 \(21\) 行,i * dp[i] 就是在把值和数量乘起来相加,相当于就是在求所有二进制数为 \(i\) 的数字总和,这和 solve2 函数是相同的

唯一的不同点在于,该函数不能从 \(0\) 转移到 \(0\),因此选出的子序列第一个数必须为 \(1\)

综上,solve 函数就是在把字符串中所有长度不超过 \(m\) **且开头为 \(1\) **的子序列全部取出来,当作二进制数,转为十进制后再全部相加

第17 - 1题(1.5 分)

假设数组 dp 长度无限制,函数 solve() 所实现的算法的时间复杂度是 O(n*2^m)。

A.对

B.错

明显根据循环可以得出时间复杂度为 \(O(n\times 2^m)\)

第17 - 2题(1.5 分)

输入 "11 2 10000000001" 时,程序输出两个数 32 和 23。

A.对

B.错

相当于在找所有长度不超过 \(2\) 的子序列当作二进制数后的总和

当长度为 \(1\),对答案有贡献的子序列只有 1,出现了 \(2\)

当长度为 \(2\),对答案有贡献的子序列有 01 / 10 / 11,出现次数分别为 \(9, 9, 1\)

对于 solve2 函数,因为该函数允许第一个选出的数为 \(0\),因此答案为 \(1_2 \times 2 + 01_2 \times 9 + 10_2 \times 9 + 11_2 \times 1 = 2 + 9 + 18 + 3 = 32\)

对于 solve1 函数,因为该函数不允许第一个选出的数为 \(0\),因此排除 01 的情况,答案为 \(1_2 \times 2 + 10_2 \times 9 + 11_2 \times 1 = 23\)

第17 - 3题(2 分)

在 n≤10 时,solve() 的返回值始终小于 4^10。

A.对

B.错

不论对于哪个函数,在字符串全为 \(1\),且 \(n = m\) 时一定可以取得最大值

因此假设 \(n = m = 10, s = 1111111111\)

答案为 \(C_{10}^1 \times 1 + C_{10}^2 \times 3 + C_{10}^3 \times 7 + \dots + C_{10}^{10} \times 1023 = 58025 \lt 4^{10}\)

第17 - 4题(3 分)

当 n=10 且 m=10 时,有多少种输入使得两行的结果完全一致?

A.1024

B.11

C.10

D.0

如果两个函数结果一致,只能说明不存在任何以 \(0\) 开头的子序列

也就是说所有 \(0\) 都必须出现在 \(1\) 之后

\(n = m = 10\) 时,方案只有 \(11\) 种:

  • 0000000000
  • 1000000000
  • 1100000000
  • 1110000000
  • 1111000000
  • \(\dots\)
  • 1111111111

第17 - 5题(3 分)

当 n <= 6 时,solve() 的最大可能返回值为( )。

A.65

B.211

C.665

D.2059

同上面最后一道判断,最大可能返回值即字符串全 \(1\) 的情况

\(C_6^1 \times 1_2 + C_6^2 \times 11_2 + C_6^3 \times 111_2 + \dots + C_6^6 \times 111111_2 = 665\)

第17 - 6题(3 分)

若 n = 8,m = 8,solve 和 solve2 的返回值的最大可能的差值为( )。

A.1477

B.1995

C.2059

D.2187

两个函数的差值就等于以 \(0\) 开头的子序列对答案的贡献

\(n=m=8\) 时,为了能让子序列当作二进制时的权值尽可能大,因此此时字符串方案应该是 01111111

考虑不同长度子序列的数量及其贡献:

  • 长度为 \(2\) 时,选 \(1\)\(1\),答案为 \(C_7^1 \times 01_2\)
  • 长度为 \(3\) 时,选 \(2\)\(1\),答案为 \(C_7^2 \times 011_2\)
  • 长度为 \(4\) 时,选 \(3\)\(1\),答案为 \(C_7^3 \times 0111_2\)
  • \(\dots\)
  • 长度为 \(8\) 时,选 \(7\)\(1\),答案为 \(C_7^7 \times 01111111_2\)

最终答案即总和,求解可得 \(2059\)

第18题 (14.5 分)

img

img

img

这是一道树上双哈希 + 埃氏筛的题目

首先关注 init 函数

  • p 数组就是埃氏筛,表示每个位置的数字是否是素数
  • p1[i] 表示 \(B1^i \bmod P1\)
  • p2[i] 表示 \(B2^i \bmod P2\)

然后观察 H 这个结构体的内部,其中有三个成员变量 h1, h2, l

初始化函数中,l = 1 恒成立,h1h2 会根据传入的 b 不同而出现差值为 \(1\) 的区别

在加法运算符的重载函数中,发现加法得到的 l 为两个结构体的 l 相加,而 h1h2 两成员变量的变化过程相似

对于 h1,相当于将加法左侧结构体的 h1 乘上 \(B1^{\text{右侧结构体的 l}}\) 之后,再加上右侧结构体的 h1

明显这里 B1 表示的就是哈希的底数,l 表示哈希多项式的长度(即数字个数),加法操作相当于是把两个哈希多项式合并在了一起,因此左侧的多项式每一项都需要乘上 底数 的 右侧多项式项数 次方,再把右侧多项式加上

因此 h1h2 两个数字只是在不同哈希底数和模数下得到的两个哈希值,这里做的是双哈希

然后等号的运算符重载函数只是为了判断两个哈希值是否相等,小于号则定义了哈希结构体的排序方法

最后回到 solve 函数,这个 for 循环是倒着做的,根据 h[i] = H(p[i]) 可以发现,初始时 h[i] 只会出现两种情况:

  • 一种是 \(i\) 是质数时,h1 = K1+1 = 1h2 = K2+1 = 14l = 1
  • 一种是 \(i\) 不是质数时,h1 = K1 = 0h2 = K2 = 13l = 1

然后观察第 59 和 61 行的转移,发现 h[i] 的变化只会跟 h[2 * i]h[2 * i + 1] 这两个位置有关系

通过 \(i, 2i, 2i+1\) 三个数字的关系,不难想到数组模拟二叉树时的编号顺序,\(2i\)\(2i+1\) 分别是 \(i\) 的左右儿子

因此第 \(58\)2 * i + 1 <= n 就是表示 \(i\) 有左右儿子,而第 \(60\)2 * i <= n 表示 \(i\) 只有左儿子

然后发现在左右儿子都存在时,h[i] = h[2*i] + h[i] + h[2*i+1],先左儿子,再根结点,再右儿子;当只有左儿子时,h[i] = h[2*i] + h[i],即先左儿子,再根节点

所以哈希过程就是把当前子树的中序遍历序列给哈希掉

后面 h[1].h1 就是根结点的哈希值

最后的排序+去重,输出去重后不同哈希结构体的数量,其实就是在求二叉树上存在多少棵不同的子树

第18 - 1题(1.5 分)

假设程序运行前能自动将 maxn 改为 n+1,所实现的算法的时间复杂度是 O(n log n)。

A.对

B.错

埃氏筛的时间复杂度为 \(O(n\log\log n)\),排序函数的时间复杂度是 \(O(n\log n)\),排序函数的时间复杂度级别更高,所以整体时间复杂度为 \(O(n\log n)\)

第18 - 2题(1.5 分)

时间开销的瓶颈是 init() 函数。

A.对

B.错

同上一题,时间复杂度取决于排序函数,所以瓶颈在 solve 函数

第18 - 3题(1.5 分)

若修改常数 B1 或 K1 的值,该程序可能会输出不同的结果。

A.对

B.错

修改常数 B1 或 K1,相当于把第一个哈希值的哈希过程给修改了

第 64 行输出的是整棵树的第一个哈希值,因此这个值很可能会发生变化

第18 - 4题(3 分)

在 solve() 函数中,h[] 的合并顺序可以看作是:

A.二叉树的 BFS 序

B.二叉树的先序遍历

C.二叉树的中序遍历

D.二叉树的后序遍历

根据上面的分析,h 的合并可以看作是在合并子树的中序遍历序列的哈希多项式

第18 - 5题(3 分)

输入 "10",输出的第一行是( )?

A.83

B.424

C.54

D.110101000

问的是输出的第一行,因此需要求出根结点 h1 的值

但根据上面的分析,根结点 h1 的哈希值其实就是整棵树每个结点 h1 的中序遍历序列的哈希值

这个哈希值乘的底数是 \(B1 = 2\),一开始每个结点 h1 的值取决于该结点所在编号的数字是否是素数,如果是则为 \(1\),不是则为 \(0\)

所以 h1 最终的结果可以简单看作是整棵树中序遍历序列当作二进制数,转为十进制之后的结果

我们可以先把 \(n=10\) 时的这棵树画出来,其中黑色数字表示编号,蓝色数字表示 h1 的初始值

image-20240921234605425

这棵树的中序遍历为 \(8,4,9,2,10,5,1,6,3,7\),对应的值分别是 \(0,0,0,1,0,1,0,0,1,1\)

\(0001010011\) 转为二进制,得 \(83\)

第18 - 6题(4 分)

(4分)输出 "16",输出的第二行是( )?

A.7

B.9

C.10

D.12

第二行表示的是有多少个不同的哈希结构体,换句话说也就是这棵树上有多少棵本质不同的子树

\(n=16\) 的树画出,蓝色数字表示 h1 的初始值

image-20240921235219438

接下来以每个结点作为根结点,看一遍有多少棵本质不同的子树即可

下面打勾的就是本质不同的子树的根结点方案之一,总共 \(10\)

image-20240921235401870

第19题 (15 分)

(序列合并)有两个长度为 N 的单调不降序列 A 和 B,序列的每个元素都是小于 10^9 的非负整数。在 A 和 B 中各取一个数相加可以得到 N^2 个和,求其中第 K 小的和。上述参数满足 N <= 10^5 和 1 <= K <= N^2。

img

img

首先可以考虑完成 upper_bound 函数,这其实是一个 STL 函数,但这里让你手动实现其内部的二分。upper_bound(l, r, x) 函数的含义是在一段连续地址 \([l, r)\) 中查找出第一个 \(\gt x\) 的位置。但如果说你比较担心这道题的 upper_bound 和我们平时接触的二分函数含义不一样,可以先看下面的分析:

根据题意,要找到排名为 \(k\) 的和,因此考虑二分答案 mid,然后找出有多少个和是不超过 mid 的,根据得到的数量来判断答案要往大了找还是往小了找

先看第五个空,这里的条件一旦满足就会执行 l = mid + 1,说明答案一定大于此时的 mid,所以可以判断是因为 <= mid 的和的数量严格小于 k,即 get_rank(mid) < k

然后就可以推导出 get_rank(sum) 这个函数就是在求 <= sum 的和的数量。只需要对于每个 a[i],求出有多少个 b[j] 满足 a[i] + b[j] <= sum,移项得 b[j] <= sum - a[i],因此只需要在 b 数组里找不超过 sum - a[i] 的数字有多少个就可以,所以第 26 行代码应该要通过 upper_bound 函数找到 b 数组中 > sum - a[i] 的第一个地址,然后减去 b 的首地址,才能够获得想要的答案

所以从这里可以推出 upper_bound(l, r, x) 是在一段连续地址 \([l, r)\) 中二分查找出第一个 \(\gt x\) 的位置,注意左闭右开

第19 - 1题(3 分)

①处应填

A.an-a

B.an-a-1

C.ai

D.ai+1

这里是 upper_bound 函数的右边界,因为要进行二分,所以 r 一开始应该表示的是这段左闭右开的地址内共有多少个数字,即尾地址 an 减首地址 a

第19 - 2题(3 分)

②处应填

A.a[mid] > ai

B.a[mid] >= ai

C.a[mid] < ai

D.a[mid] <= ai

不妨先看 else,想想什么时候会让 l = mid + 1

因为现在要找第一个 > ai 的数字,所以只有当当前位置的数字 a[mid] <= ai 时,才能断定答案一定在 mid 右侧

所以反过来,if 里就应该填 a[mid] > ai

第19 - 3题(3 分)

③处应填

A.a+l

B.a+l+1

C.a+l-1

D.an-l

最后要返回一个地址,所以应该是从首地址 a 开始往后移动 l/r 个位置的地址

其实这里写法有很多,像是 a + la + r&a[l]&a[r] 都可以

第19 - 4题(3 分)

④处应填

A.a[n-1]+b[n-1]

B.a[n]+b[n]

C.2 * maxn

D.maxn

solve 函数二分的是最终答案,如果 \(k = n^2\),那么答案就是最大和,应该是两数组最大值之和,即 a[n-1] + b[n-1]

第19 - 5题(3 分)

⑤处应填

A.get_rank(mid) < k

B.get_rank(mid) <= k

C.get_rank(mid) > k

D.get_rank(mid) >= k

详见上面的分析

第20 题 (15 分)

(次短路)

已知一个有 n 个点 m 条边的有向图 G,并且给定图中的两个点 s 和 t,求次短路(长度严格大于最短路的最短路径)。如果不存在,输出一行 "-1"。如果存在,输出两行,第一行表示次短路的长度,第二行表示次短路的一个方案。

img

img

(一道模板次短路算法题,但写法略微抽象了些,但也还好)

首先明确什么是次短路:除了最短路以外的最短路(除最优解以外的最优解就是次优解)

所以次短路算法的总体逻辑就是在最短路算法的基础上分类讨论

假设现在正在进行最短路算法的松弛,假设 \(a\)\(b\) 有一条长度为 \(w\) 的边,现在要根据 \(a\) 的答案来更新 \(b\) 的答案:

  • 如果 dis[b] > dis[a] + c,说明现在能够借助 \(a\) 来松弛 \(b\) 的最短路答案,目前走到 \(b\) 点的最优解变成了从 \(a\) 点经过 \(a \rightarrow b\) 这条边过来,所以在这一步松弛之前dis[b] 有可能作为 \(b\) 点的次优解
  • 如果 dis[b] < dis[a] + c,说明现在无法借助 \(a\) 来松弛 \(b\) 的最短路答案,但此时从 \(a\) 点经过 \(a \rightarrow b\) 这条边过来的方案也是有可能作为 \(b\) 点的次优解的
  • 如果 dis[b] == dis[a] + c,题干中有提到次短路是“长度严格大于最短路的最短路径”,所以相等的情况不用管

整个算法就是在这个分类讨论的基础上执行的,在所有可能的次优解中找一个长度最小的方案,就是次短路

接下来看代码本身

add 函数很明显是链式前向星加一条 \(a\rightarrow b\) 的长度为 \(c\) 的边

out 函数是找到次短路之后输出这条路径的递归函数

然后观察 main 函数,所有点的下标都 \(-1\) 后变成 \(0 \sim n-1\) 的范围了,然后进入 solve 函数,一个普通的 Dijkstra 堆优化写法,然后只要队列不为空就不断松弛出去,重点在于第 37、38 行和第 46 到 51 行这一段

先看第 37、38 行,dis2pre2 两个指针分别指向了 dis + npre + n 的位置,这也是为什么 dispre 数组要开两倍空间,相当于前面 \(0 \sim n-1\) 的下标给自己用,而后面 \(n \sim 2n-1\) 的下标交给了 dis2pre2 两个指针用。所以可以猜测 dis2pre2 就是用来记录次短路的答案和路径的

就相当于这里建立了一张分层图,\(0 \sim n-1\) 在正常跑最短路,而 \(n \sim 2n-1\) 则是记录上一层每个对应的点的次短路,同一个点在两层间的编号差值为 \(n\)

然后看 47 行的参数传递格式,可以知道 upd(a, b, d, q) 相当于是把松弛的过程写成了一个函数,在看 \(a\)\(b\) 这两个点的最短路能否更新为 \(d\),通过函数返回值来判断是否成功松弛

如果成功松弛,根据上面的分类讨论,原本到 \(b\) 点的答案就成了次优解,所以第一个空这里 b < n 就是在判断现在 \(b\) 这个点是否还在求最短路的这一层,如果是,则可以更新 \(b\) 的次短路;如果不是,说明这一次松弛本来就是在更新 \(b\) 的次短路,就不用管了

而如果没有成功松弛,根据上面的分类讨论,当前从 \(a\) 出发的这种方案可能成为次优解,所以第四个空就是在这种情况下更新 \(b\) 的次短路的

最后如果说存在次短路,则从第二层的终点 n+t 开始往回找出整条路径,递归输出即可

第20 - 1题(3 分)

①处应填

A.upd(pre[b], n + b, dis[b], q)

B.upd(a, n + b, d, q)

C.upd(pre[b], b, dis[b], q)

D.upd(a, b, d, q)

根据上面的分析,这里是在 \(b\) 进行松弛之前,把它原本的最短路当作次短路来更新答案的

原本到 \(b\) 的最短路的前一个点可以通过 pre[b] 得到,变成次短路后就应该成为第二层的点,即 \(n+b\),此时次短路长度为 dis[b]

第20 - 2题(3 分)

②处应填

A.make_pair(-d, b)

B.make_pair(d, b)

C.make_pair(b, d)

D.make_pair(-b, d)

这里就是考 Dijkstra 堆优化情况下,优先队列要怎么放元素

pair 会默认根据 first 关键词从小到大排,first 相同时按照 second 从小到大排

但因为代码里的优先队列是用的大根堆,所以会按 first 从大到小排先

而 Dijkstra 堆优化更多的是用小根堆来求出距离起点最近的未访问过的点进行松弛

所以为了能在大根堆中实现小根堆的功能,这里应该把 first 关键词取反,并且 first 关键词表示当前点与起点之间的距离,second 关键词表示当前点的编号

所以这里的 make_pair 的参数为 \(-d\)\(b\)

第20 - 3题(3 分)

③处应填

A.0xff

B.0x1f

C.0x3f

D.0x7f

这里是在初始化 dis 数组,即记录最短路的答案数组,一般是要初始化成一个无穷大的数字的

因为 int 类型最大只能存到 \(2^{31} - 1\),所以 memset 函数不能直接使用 0xff,所以排除 A

因为 dis 数组在松弛过程中会和某条边的长度相加求和,所以如果直接用 0x7f 有概率超出范围,一般也不用,排除 D

正常情况下都是选 0x3f 最为保险,但这题请注意第 74 行(上面截图里是第 72 行)以及第 7 行代码中的 inf 的定义

最后在终点次短路为 inf 时,直接判断不存在,因此我们 memset 应该这个无穷大的数字设置为与 inf 相同

\(522133279_{10} = 1f1f1f1f_{16}\)

所以这题只能选 B

第20 - 4题(3 分)

④处应填

A.upd(a, n + b, dis[a] + c, q)

B.upd(n + a, n + b, dis2[a] + c, q)

C.upd(n + a, b, dis2[a] + c, q)

D.upd(a, b, dis[a] + c, q)

根据上面的分析,这里是在松弛失败之后,把当前从 \(a\)\(b\) 且经过长度为 \(c\) 的边的这种方案当作次优解,来更新次短路的

所以这里很明显就是从 \(a\) 出发,到 \(b\) 点对应的次优解点 \(n+b\),尝试更新答案为 dis[a] + c

第20 - 5题(3 分)

⑤处应填

A.pre2[a%n]

B.pre[a%n]

C.pre2[a]

D.pre[a%n]+1

第 58 行的 else 表示的是 \(a\) 点现在是次短路这一层中的点,所以我们应该从 pre2 数组里把 \(a\) 点的上一个点找出来

明显现在 \(a \ge n\) 是成立的,所以不能直接写 pre2[a],不然会越界,只能把 \(a\) 先转回原本的编号,可以用 a % n 也可以直接 a - n

所以这个空答案有多种,可以是 pre2[a % n],可以是 pre2[a - n],也可以直接是 pre[a]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/801313.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSP-J 2024 初赛解析

时间紧任务重,可能有误,烦请指正 QwQ第1题 (2分) 32 位 int 类型的存储范围是? A. -2147483647 ~ +2147483647 B. -2147483647 ~ +2147483648 C. -2147483648 ~ +2147483647 D. -2147483648 ~ +214748364832 位 int 类型,除最高位为符号位外,剩下 31 位均为数字。但 0 的二…

《MySQL 进阶篇》二十:锁

MySQL 锁的分类,表锁和行锁有哪些类型?Author: ACatSmiling Since: 2024-09-21锁是计算机协调多个进程或线程并发访问某一资源的机制。在程序开发中会存在多线程同步的问题,当多个线程并发访问某个数据的时候,尤其是针对一些敏感的数据(比如订单、金额等),就需要保证这个…

《MySQL 进阶篇》二十一:MVCC

MySQL 是如何处理并发问题的?什么是 MVCC?MVCC 的原理是什么?Author: ACatSmiling Since: 2024-09-21什么是 MVCC MVCC:Multiversion Concurrency Control,多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在 InnoDB 的事…

15.Python基础篇-文件操作

文件的打开与关闭 第一种方法:open函数 open函数:打开一个文件,并返回文件对象。如果文件无法打开,会抛出 OSError异常。 open函数的参数介绍: file参数 要打开的文件路径。可以是绝对路径也可以是相对路径 mode参数 打开文件的模式。分为:r:只读。文件的指针会放在文件…

《MySQL 进阶篇》十七:数据库其他调优策略

MySQL 数据库的其他调优策略。Author: ACatSmiling Since: 2024-09-21数据库调优的措施 调优的目标尽可能节省系统资源,以便系统可以提供更大负荷的服务(吞吐量更大)。 合理的结构设计和参数调整,以提高用户操作响应的速度(响应速度更快)。 减少系统的瓶颈,提高 MySQL 数…

《MySQL 进阶篇》十九:事务日志

MySQL redo log 和 undo log。Author: ACatSmiling Since: 2024-09-21事务有 4 种特性:原子性、一致性、隔离性和持久性。那么事务的 4 种特性到底是基于什么机制实现呢?事务的隔离性由锁机制实现。 而事务的原子性、一致性和持久性由事务的redo log和undo log来保证。redo l…

《MySQL 进阶篇》十八:事务基础知识

MySQL 事务的定义、ACID 特性,以及事务的隔离级别。Author: ACatSmiling Since: 2024-09-21数据库事务概述 事务是数据库区别于文件系统的重要特性之一,当有了事务就会让数据库始终保持一致性,同时还能通过事务的机制恢复到某个时间点,这样可以保证已提交到数据库的修改不会…

《MySQL 进阶篇》十五:索引优化和查询优化

MySQL 索引优化,以及查询优化。Author: ACatSmiling Since: 2024-09-21数据库调优的维度:索引失效、没有充分利用所以 —— 索引建立。 关联查询太多 JOIN(设计缺陷或不得已的需求)—— SQL 优化。 服务器调优及各个参数设置(缓冲、 线程数)—— 调整 my.cnf。 数据过多 …

《MySQL 进阶篇》十六:数据库的设计规范

MySQL 数据库设计需要遵循什么规范?什么是范式?什么是反范式?Author: ACatSmiling Since: 2024-09-21为什么需要数据库设计 我们在设计数据表的时候,要考虑很多问题。比如:用户都需要什么数据?需要在数据表中保存哪些数据? 如何保证数据表中数据的正确性?当插入、删除、…

《MySQL 基础篇》十一:索引的存储结构

MySQL 索引的存储结构,以及推演过程。Author: ACatSmiling Since: 2024-09-21为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构。 常将索引比作教科书的目录部分(实际上不是完全一样),通过目录找到对应文章的页码,便可快速定位到需要的文章。MySQL 中也是…

《MySQL 基础篇》十二:InnoDB 存储引擎的数据结构

MySQL InnoDB 存储引擎的数据结构。Author: ACatSmiling Since: 2024-09-21数据库的存储结构:页 索引结构提供了高效的索引方式,不过索引信息以及数据记录都是保存在文件上的,确切说时存储在页结构中。另一方面,索引实在存储引擎中实现的,MySQL 服务器上的存储引擎负责对表…

《MySQL 基础篇》九:存储过程、流程控制和触发器

MySQL 的存储过程与函数、变量、流程控制与游标,以及触发器的使用。Author: ACatSmiling Since: 2024-09-20存储过程与函数 MySQL 从 5.0 版本开始支持存储过程和函数。存储过程和函数能够将复杂的 SQL 逻辑封装在一起,应用程序无须关注存储过程和函数内部复杂的 SQL 逻辑,而…