word转博客比较费劲,有一些公式没有转过来,后续会陆续完善,要word版本的私信我或评论我即可。
2025CSP-S初赛真题解析
- 单项选择题
答案总览:CABDDDAADDCAACB
第一题
直接列举-0代表红球,1代表蓝球
0101010101
1010101010
1001010101
1010010101
1010100101
1010101001
第二题
对于模式串 P="abacaba",我们需要计算其 next 数组。next[i] 定义为模式串 P[0...i]P[0...i] 的最长公共前后缀的长度(且数组下标从 0 开始)。注意,这里的最长公共前后缀不包括整个子串本身(即不能是自身)。
让我们逐步计算每个位置的 next 值:
i=0i=0: 子串 "a" 的最长公共前后缀(不包括自身)为 0。所以 next[0] = 0。
i=1i=1: 子串 "ab"。
前缀:["a"]
后缀:["b"]
没有公共前后缀,所以 next[1] = 0。
i=2i=2: 子串 "aba"。
前缀:["a", "ab"]
后缀:["a", "ba"]
公共前后缀:"a"(长度为1),所以 next[2] = 1。
i=3i=3: 子串 "abac"。
前缀:["a", "ab", "aba"]
后缀:["c", "ac", "bac"]
没有公共前后缀,所以 next[3] = 0。
i=4i=4: 子串 "abaca"。
前缀:["a", "ab", "aba", "abac"]
后缀:["a", "ca", "aca", "baca"]
公共前后缀:"a"(长度为1),所以 next[4] = 1。
i=5i=5: 子串 "abacab"。
前缀:["a", "ab", "aba", "abac", "abaca"]
后缀:["b", "ab", "cab", "acab", "bacab"]
公共前后缀:"ab"(长度为2),所以 next[5] = 2。
i=6i=6: 子串 "abacaba"。
前缀:["a", "ab", "aba", "abac", "abaca", "abacab"]
后缀:["a", "ba", "aba", "caba", "acaba", "bacaba"]
公共前后缀:"aba"(长度为3),所以 next[6] = 3。
因此,next 数组为:
next[0]=0next[1]=0next[2]=1next[3]=0next[4]=1next[5]=2next[6]=3next[0]next[1]next[2]next[3]next[4]next[5]next[6]=0=0=1=0=1=2=3
所以,对于模式串 P="abacaba"P="abacaba",其 next 数组为:
next=[0,0,1,0,1,2,3]next=[0,0,1,0,1,2,3]
验证:
子串 "abacab"(i=5)的最长公共前后缀是 "ab"(前缀 "ab" 和后缀 "ab"),长度为2,正确。
子串 "abacaba"(i=6)的最长公共前后缀是 "aba"(前缀 "aba" 和后缀 "aba"),长度为3,正确。
第三题
我们需要在大小为16(下标0-15)的数组上构建满线段树(即完全二叉树),并查询区间 [3, 11]。要求最少需要访问的结点数(包括路径上的父结点和完全包含在查询区间内的结点)。
线段树结构:
叶子结点:16个,对应数组下标0到15。
树的高度:log2(16)=4,所以有4层(层数从0开始,根为第0层,叶子为第3层)。
结点编号:通常从根开始(下标为1),但这里我们关注的是逻辑结构,不涉及具体存储。
查询区间 [3,11]:
我们需要找到覆盖区间 [3,11] 的最少结点集合(这些结点要么完全包含在 [3,11] 内,要么是路径上的父结点)。
标准线段树查询方法:
从根结点(覆盖[0,15])开始递归:
如果当前结点区间完全包含在查询区间内,则直接返回(计数1个结点)。
否则,如果与查询区间有交集,则递归左右子树。
我们模拟查询过程(注意:线段树每个结点代表一个区间 [l, r]):
根结点 [0,15]:与 [3,11] 有交集,不完全包含(因为0-2和12-15不在查询内),所以需要递归左右子树。
访问根结点(计数1)。
左子树 [0,7]:与 [3,11] 有交集(部分在),不完全包含(因为0-2不在),递归左右。
访问结点 [0,7](计数2)。
右子树 [8,15]:与 [3,11] 有交集(部分在),不完全包含(因为12-15不在),递归左右。
访问结点 [8,15](计数3)。
现在递归到更细的区间:
对于左子树 [0,7]:
左子结点 [0,3]:与 [3,11] 有交集(部分在),不完全包含(因为0-2不在),递归左右。
访问结点 [0,3](计数4)。
右子结点 [4,7]:完全包含在 [3,11] 内(因为4-7都在[3,11]中),所以直接返回(计数5)。
对于右子树 [8,15]:
左子结点 [8,11]:完全包含在 [3,11] 内(8-11都在),直接返回(计数6)。
右子结点 [12,15]:与 [3,11] 无交集(12>11),不访问(停止递归)。
现在回到 [0,3] 的递归:
[0,3] 的左子结点 [0,1]:与 [3,11] 无交集(1<3),不访问。
[0,3] 的右子结点 [2,3]:与 [3,11] 有交集(部分在),不完全包含(2不在),递归左右。
访问结点 [2,3](计数7)。
继续 [2,3]:
左子结点 [2,2]:与 [3,11] 无交集(2<3),不访问。
右子结点 [3,3]:完全包含在 [3,11] 内(3在),直接返回(计数8)。
所以总共访问了8个结点。
列出所有访问的结点(区间):
[0,15] (根)
[0,7] (左子树)
[8,15] (右子树)
[0,3] ([0,7]的左)
[4,7] ([0,7]的右,完全包含)
[8,11] ([8,15]的左,完全包含)
[2,3] ([0,3]的右)
[3,3] ([2,3]的右,完全包含)
因此,最少需要访问8个结点。
验证:
实际上,对于区间查询,最少访问结点数可以通过分析线段树的结构得到。一般地,对于区间 [l, r],访问的结点数最多为 O(log n),但这里需要包括路径上的父结点和完全包含的结点。
另一种方式:线段树查询时,访问的结点分为两类:
完全包含的结点(直接返回,不再递归)。
部分包含的结点(需要递归)。
对于 [3,11]:
完全包含的结点:[4,7], [8,11], [3,3](共3个)。
部分包含的结点(路径上的父结点):[0,15], [0,7], [8,15], [0,3], [2,3](共5个)。
所以总数为3+5=8。
因此,最少需要访问8个结点。
第四题
Trie 树的结构:
每个结点代表一个字符(根结点不代表字符)。
从根到某个结点的路径构成一个前缀。
共享公共前缀的字符串会共享结点。
逐步插入并计数结点(包括根结点):
初始:只有根结点(计数1)。
插入 "cat":
c(新结点,计数2)
a(新结点,计数3)
t(新结点,计数4)
路径:根 -> c -> a -> t
插入 "car":
已有 "c"(共享),"a"(共享)
在 "a" 下添加 "r"(新结点,计数5)
路径:根 -> c -> a -> r
插入 "cart":
已有 "c"、"a"、"r"(共享)
在 "r" 下添加 "t"(新结点,计数6)
路径:根 -> c -> a -> r -> t
插入 "case":
已有 "c"(共享),"a"(共享)
在 "a" 下,已有子结点 "r" 和 "t"(但这里需要 "s"),所以添加 "s"(新结点,计数7)
在 "s" 下添加 "e"(新结点,计数8)
路径:根 -> c -> a -> s -> e
插入 "dog":
d(新结点,计数9)
o(新结点,计数10)
g(新结点,计数11)
路径:根 -> d -> o -> g
插入 "do":
已有 "d"(共享),"o"(共享)
在 "o" 标记为单词结束(但不需要新结点,因为已存在)
路径:根 -> d -> o(共享)
总结所有结点:
根结点(1个)
"c"(1个)
"a"(在"c"下,1个)
"t"(在"a"下,1个)-> "cat"的结尾
"r"(在"a"下,1个)-> "car"的结尾
"t"(在"r"下,1个)-> "cart"的结尾
"s"(在"a"下,1个)
"e"(在"s"下,1个)-> "case"的结尾
"d"(1个)
"o"(在"d"下,1个)-> "do"的结尾
"g"(在"o"下,1个)-> "dog"的结尾
所以,总结点数 = 1(根) + 1(c) + 1(a) + 1(t for cat) + 1(r) + 1(t for cart) + 1(s) + 1(e) + 1(d) + 1(o) + 1(g) = 11个。
验证:
列出所有唯一结点(字符):
根
c
a (在c下)
t (在a下,用于cat)
r (在a下,用于car)
t (在r下,用于cart) (注意:这个t与cat的t不同,因为父结点不同)
s (在a下,用于case)
e (在s下,用于case)
d
o (在d下)
g (在o下,用于dog)
确实有11个结点。
注意:"do"不需要新结点,因为"o"已经存在(来自"dog")。
因此,构建完成后,Trie树共有11个结点(包括根节点)。
第五题
如果m=0,那么最多为n!种
第六题
没有啥说的了,超级简单
第七题
想要最小,就是相邻编号的两节点挨在一起,所以形成了一个链,自上而下为12345678,俩俩之间差1,一共为7.
第八题
二叉搜索树的性质:
对于任意结点,左子树的所有结点值小于该结点,右子树的所有结点值大于该结点。
后序遍历:左子树 → 右子树 → 根
前序遍历:根 → 左子树 → 右子树
步骤:
识别根结点:后序遍历的最后一个元素是根结点。所以根是6。
划分左右子树:
在序列中,从左开始找到第一个大于根的元素(因为BST中右子树所有结点大于根)。
序列:2, 5, 4, 8, 12, 10, 6(根)
从左扫描:2<6, 5<6, 4<6,然后8>6(所以第一个大于根的是8)。
因此,左子树:2,5,4(所有小于6的部分)
右子树:8,12,10(所有大于6的部分)
注意:后序遍历中,左子树先出现,然后右子树,最后根。
递归构建左右子树:
左子树的后序:2,5,4 → 根是4(最后一个)
右子树的后序:8,12,10 → 根是10(最后一个)
详细递归:
根:6
左子树(后序:2,5,4):
根:4(最后一个)
划分:从左扫描,2<4, 5>4(第一个大于4的是5)→ 所以左子树:2(小于4);右子树:5(大于4)
左子结点:2(无子树)
右子结点:5(无子树)
所以左子树结构:
4
/
2 5
右子树(后序:8,12,10):
根:10(最后一个)
划分:从左扫描,8<10, 12>10(第一个大于10的是12)→ 所以左子树:8(小于10);右子树:12(大于10)
左子结点:8(无子树)
右子结点:12(无子树)
所以右子树结构:
10
/
8 12
整体树结构:
6
/
4 10
/ \ /
2 5 8 12
前序遍历:根 → 左子树 → 右子树
根:6
左子树(以4为根)的前序:4, 2, 5
右子树(以10为根)的前序:10, 8, 12
所以整体前序:6, 4, 2, 5, 10, 8, 12
第九题
就挨个带就完事了,超级简单,注意是01背包即可。
第十题
12和1是公共祖先???出题人如此弱智,这种题根本没有难度。
第十一题
主定理带进去算,发现log22=1,n^1又小于n^2,故时间复杂度为O(n2)

.
第十二题
堆自动排序,这都太简单了,没有难度,不说了。
第十三题
两种方法,第一种考试用的,第二种AI写的:
第一种:
我们需要找出1到1000之间不能被2、3、5中任意一个整除的整数个数。
这等价于求:总数 - 能被2或3或5整除的个数。
步骤1:定义集合
设:
AA: 能被2整除的数的集合
BB: 能被3整除的数的集合
CC: 能被5整除的数的集合
我们要求的是:∣Ac∩Bc∩Cc∣

,即不能被2、3、5任意一个整除的数的个数。
步骤2:使用容斥原理
总数 N=1000N=1000
能被2或3或5整除的个数:
∣A∪B∪C∣=∣A∣+∣B∣+∣C∣-∣A∩B∣-∣A∩C∣-∣B∩C∣+∣A∩B∩C∣∣A∪B∪C∣=∣A∣+∣B∣+∣C∣-∣A∩B∣-∣A∩C∣-∣B∩C∣+∣A∩B∩C∣

计算各集合大小:
∣A∣=⌊10002⌋=500∣A∣=⌊21000⌋=500

∣B∣=⌊10003⌋=333∣B∣=⌊31000⌋=333

∣C∣=⌊10005⌋=200∣C∣=⌊51000⌋=200

∣A∩B∣=⌊1000lcm(2,3)⌋=⌊10006⌋=166∣A∩B∣=⌊lcm(2,3)1000⌋=⌊61000⌋=166

∣A∩C∣=⌊1000lcm(2,5)⌋=⌊100010⌋=100∣A∩C∣=⌊lcm(2,5)1000⌋=⌊101000⌋=100

∣B∩C∣=⌊1000lcm(3,5)⌋=⌊100015⌋=66∣B∩C∣=⌊lcm(3,5)1000⌋=⌊151000⌋=66

∣A∩B∩C∣=⌊1000lcm(2,3,5)⌋=⌊100030⌋=33∣A∩B∩C∣=⌊lcm(2,3,5)1000⌋=⌊301000⌋=33

代入容斥公式:
∣A∪B∪C∣=500+333+200-166-100-66+33=(500+333+200)-(166+100+66)+33=1033-332+33=734∣A∪B∪C∣=500+333+200-166-100-66+33=(500+333+200)-(166+100+66)+33=1033-332+33=734

步骤3:求不能被2、3、5整除的个数
不能被2、3、5整除的个数=1000−734=266不能被2、3、5整除的个数=1000−734=266
第二种-直接计算余数
1到1000中,能被2、3、5整除的数的周期为30(因为lcm(2,3,5)=30)。
在每个30的周期中,不能被2、3、5整除的数有:
总30个数中,去掉能被2、3、5整除的(容斥原理):
能被2整除:15个
能被3整除:10个
能被5整除:6个
能被2和3整除(即6):5个
能被2和5整除(即10):3个
能被3和5整除(即15):2个
能被2、3、5整除(即30):1个
所以:
∣A∪B∪C∣=15+10+6-5-3-2+1=22∣A∪B∪C∣=15+10+6-5-3-2+1=22

因此,每个周期中不能被2、3、5整除的个数为 30−22=830−22=8。
1到1000中,有:
⌊100030⌋=33个完整周期(共990个数)⌊301000⌋=33个完整周期(共990个数)
剩余10个数:991,992,...,1000
每个完整周期有8个满足条件的数,所以33个周期有 33×8=26433×8=264 个。
再检查991~1000:
991: 不能被2、3、5整除?
991÷2=495.5(否),991÷3=330.333(否),991÷5=198.2(否)→ 是
992: 能被2整除(否)
993: 能被3整除(993÷3=331)→ 否
994: 能被2整除(否)
995: 能被5整除(否)
996: 能被2、3整除(否)
997: 检查:不能被2、3、5整除?
997÷2=498.5,997÷3=332.333,997÷5=199.4 → 是
998: 能被2整除(否)
999: 能被3整除(否)
1000: 能被2、5整除(否)
所以991和997满足,共2个。
因此总数:264+2=266264+2=266。
第十四题
也是简单,普通递归的缺点是什么?就是未重复利用,导致栈溢出或是时间超标。
第十五题
挨个带就行了,但是小技巧是先带截止时间最小的,因为最优的理想方案是任务全部完成,所以肯定先往好了整。
1619

被折叠的 条评论
为什么被折叠?



