2025_CSP-S_初赛解析(前十五题)

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之间不能被235中任意一个整除的整数个数。
这等价于求:总数 - 能被235整除的个数

步骤1:定义集合

设:

AA: 能被2整除的数的集合

BB: 能被3整除的数的集合

CC: 能被5整除的数的集合

我们要求的是:Ac∩Bc∩Cc

,即不能被2、3、5任意一个整除的数的个数。

步骤2:使用容斥原理

总数 N=1000N=1000

能被2或3或5整除的个数:
ABC=A+B+C-A∩B-A∩C-B∩C+A∩B∩CABC=A+B+C-AB-AC-BC+ABC

计算各集合大小:

A=10002=500A=21000=500

B=10003=333B=31000=333

C=10005=200C=51000=200

A∩B=1000lcm(2,3)=10006=166AB=lcm(2,3)1000=61000=166

A∩C=1000lcm(2,5)=100010=100AC=lcm(2,5)1000=101000=100

B∩C=1000lcm(3,5)=100015=66BC=lcm(3,5)1000=151000=66

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

代入容斥公式:

ABC=500+333+200-166-100-66+33=(500+333+200)-(166+100+66)+33=1033-332+33=734ABC=500+333+200-166-100-66+33=(500+333+200)-(166+100+66)+33=1033-332+33=734

步骤3:求不能被2、3、5整除的个数

不能被235整除的个数=1000−734=266不能被235整除的个数=1000734=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个
所以:

ABC=15+10+6-5-3-2+1=22ABC=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。

第十四题

也是简单,普通递归的缺点是什么?就是未重复利用,导致栈溢出或是时间超标。

第十五题

挨个带就行了,但是小技巧是先带截止时间最小的,因为最优的理想方案是任务全部完成,所以肯定先往好了整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值