🔒 答案已锁定

📖 答案解析 — 数字守艺人(非遗文化) — 困难2

算法应用主题赛 · Python · 共 14 题(5单选+5多选+4编程)· 满分 100 分

第 1 题 单选题 2 分
某非遗文化节模拟系统中,需要追踪多个体验摊位的状态变化(空闲/占用/等待队列)。每个摊位每个时间步都需要更新状态。对于 m 个摊位、模拟 t 个时间步,以下关于这种模拟算法时间复杂度的描述,哪个是正确的?
A. 始终为 O(1),与 m 和 t 无关
B. 每步需遍历所有摊位更新状态,总复杂度为 O(m × t)
C. 摊位之间相互独立,总复杂度为 O(m + t)
D. 可以使用二分查找优化到 O(log m × t)
✅ 正确答案:B
💡 考点解析:模拟算法的时间复杂度分析——每步更新所有摊位状态需要遍历 m 个摊位,持续 t 个时间步,总复杂度为 O(m × t)
第 2 题 单选题 2 分
刺绣技艺以"师傅带徒弟"的模式传承。已知第1年只有1位师傅,每年每位满3年经验的艺人培养1名新学徒。现使用递推数组 m[i][n] 表示第n年、具有i年经验的人数(i=0表示新学徒,i=1,2表示不同经验年数的学徒,i=3表示3年及以上的师傅)。关于递推关系,以下哪个是错误的?
A. m[0][n] = m[3][n-1](新学徒由师傅培养)
B. m[1][n] = m[0][n-1](去年新学徒变为1年学徒)
C. m[3][n] = m[3][n-1] + m[2][n-1](师傅保留 + 2年学徒晋升)
D. m[0][n] = m[0][n-1] + m[1][n-1](新学徒由所有学徒共同培养)
✅ 正确答案:D
💡 考点解析:递推关系的建立——只有师傅(3年以上经验)才能培养新学徒,学徒不能培养新人。D错误地将学徒也计为培养者
第 3 题 单选题 2 分
篆刻排列问题中,需要将 n 块印章排列成一行,要求相邻印章的风格值之和为质数。使用DFS回溯求解时,当搜索到某一步发现当前部分排列已经无法继续(剩余未使用的印章风格值与当前最后一个印章的值的和都不是质数),应该执行什么操作?
A. 继续尝试下一个印章,直到找到可行解
B. 回溯到上一步,撤销当前选择,尝试其他未使用过的印章
C. 直接输出"无解"并结束程序
D. 随机打乱剩余印章的顺序重新尝试
✅ 正确答案:B
💡 考点解析:回溯搜索(DFS+剪枝)——当当前路径无法继续时,应回溯(backtrack)到上一个决策点,恢复状态并尝试其他分支
第 4 题 单选题 2 分
在递归求解非遗技艺传承问题时,发现很多子问题被重复计算。以下哪种技术最适合优化这种重复计算问题?
A. 使用 while 循环代替递归
B. 使用记忆化搜索(memoization),将已计算过的结果存储起来避免重复计算
C. 增大递归深度限制
D. 使用 global 关键字声明变量
✅ 正确答案:B
💡 考点解析:记忆化搜索(memoization)——将计算结果缓存,当相同输入再次出现时直接返回缓存结果,避免重复递归计算
第 5 题 单选题 2 分
对于 n 块木雕的排列问题(n <= 10),如果使用枚举法(穷举所有排列)和使用带剪枝的DFS回溯两种方法,以下哪个说法是正确的?
A. 枚举法一定比DFS回溯更快
B. DFS回溯在加入剪枝条件后,通常可以跳过大量无效分支,比纯枚举法更高效
C. 两种方法的时间复杂度完全相同
D. 对于 n <= 10 的小规模问题,枚举法无法在合理时间内完成
✅ 正确答案:B
💡 考点解析:算法效率比较——DFS回溯配合剪枝可以大幅减少搜索空间,效率优于纯枚举(全排列),尤其在约束较强时效果显著
第 6 题 多选题 4 分
在非遗文化数字化项目中,经常使用模拟算法来还原工艺制作过程。以下关于模拟算法的描述,哪些是正确的?(多选)
A. 模拟算法通过按照时间顺序或规则逐步更新系统状态,来复现真实过程
B. 模拟算法需要明确定义系统的初始状态、状态转移规则和终止条件
C. 模拟算法的结果一定是100%精确的,与真实情况完全一致
D. 在设计模拟算法时,需要仔细处理边界情况和状态转换的时序
✅ 正确答案:ABD
💡 考点解析:模拟算法——模拟按规则逐步更新状态(A对);需要初始状态、转移规则、终止条件(B对);模拟是对现实的近似,不一定100%精确(C错);边界和时序处理至关重要(D对)
第 7 题 多选题 4 分
关于贪心算法在非遗活动调度中的应用,以下哪些描述是正确的?(多选)
A. 贪心算法在每一步都做出当前看来最优的选择,期望最终得到全局最优解
B. "区间调度问题"(安排尽量多的不冲突活动)使用"按结束时间排序后依次选取"的贪心策略可以得到全局最优解
C. 贪心算法适用于所有优化问题,总能得到最优解
D. 贪心算法的优点是实现简单、运行高效,但需要证明贪心策略的正确性
✅ 正确答案:ABD
💡 考点解析:贪心算法——每步取局部最优(A对);区间调度贪心可得最优解(B对);贪心并非对所有问题都适用(C错);贪心实现简单但需证明正确性(D对)
第 8 题 多选题 4 分
关于递推算法,以下哪些描述是正确的?(多选)
A. 递推的核心是根据已知的初始值和递推关系式,逐步计算出后续各项的值
B. 递推通常使用循环(for 或 while)实现,相比递归没有函数调用栈的开销
C. 递推关系式可以是一维的(如斐波那契数列),也可以是多维的(如使用多个数组相互递推)
D. 所有递推问题都可以用递归实现,但递归不一定能转化为递推
✅ 正确答案:ABC
💡 考点解析:递推算法——从初始值按关系推导(A对);递推使用循环无递归栈开销(B对);递推关系可以是多维的(C对);递归和递推在理论上可以相互转化(D错,大多数递归可以转化为递推)
第 9 题 多选题 4 分
在解决非遗图案设计等组合优化问题时,需要选择合适的搜索策略。以下关于不同搜索策略的描述,哪些是正确的?(多选)
A. 深度优先搜索(DFS)适合解决需要找到所有解或搜索树深度较大的问题
B. 广度优先搜索(BFS)适合解决最短路径/最少步数问题
C. 回溯法在DFS基础上加入了剪枝条件,可以避免搜索无效分支
D. 对于所有搜索问题,DFS总是优于BFS
✅ 正确答案:ABC
💡 考点解析:搜索策略——DFS适合全解搜索和深层搜索(A对);BFS适合最短路径(B对);回溯=DFS+剪枝(C对);没有哪种策略对所有问题都最优(D错)
第 10 题 多选题 4 分
在非遗算法问题的求解中,以下哪些算法优化技巧可以有效减少计算量?(多选)
A. 剪枝:在搜索过程中提前排除不可能产生最优解的分支
B. 记忆化:将已计算过的子问题结果存储起来,避免重复计算
C. 预处理:在正式算法运行前,先对数据进行排序、筛选或计算辅助信息
D. 增大递归深度:通过增加递归深度让程序运行更多次,从而找到更优解
✅ 正确答案:ABC
💡 考点解析:算法优化——剪枝减少搜索分支(A对);记忆化避免重复计算(B对);预处理提高后续效率(C对);增大递归深度不能优化算法,只会增加运行时间和内存消耗(D错)
第 11 题 编程操作题 15 分

题目描述:
刺绣(如苏绣、湘绣等)是珍贵的非物质文化遗产,其技艺通过"师傅带徒弟"的方式代代相传。假设某刺绣流派的传承规律如下:
- 第1年:只有1位创始师傅(经验 >= 3年)
- 每年年末统计一次人员变化
- 每位拥有3年及以上经验的艺人(师傅),每年培养 1 名新学徒(经验 = 0年)
- 学徒每年积累1年经验:0年经验 → 1年经验 → 2年经验 → 3年及以上经验(成为师傅)
- 艺人终身从事该技艺,不会退出
请使用递推算法,计算第 n 年时该流派的总从业人数。

输入格式:

输出格式:
示例输入1:
1
示例输出1:
1
示例输入2:
7
示例输出2:
10
示例输入3:
10
示例输出3:
26
递推提示:
定义四个数组:m0(新学徒)、m1(1年经验)、m2(2年经验)、m3(3年及以上经验/师傅)。
递推关系:
- m0[n] = m3[n-1](新学徒由师傅培养)
- m1[n] = m0[n-1](去年新学徒晋升)
- m2[n] = m1[n-1](1年学徒晋升)
- m3[n] = m3[n-1] + m2[n-1](师傅保留 + 2年学徒晋升为师傅)
- 总人数 = m0[n] + m1[n] + m2[n] + m3[n]

示例输入:

示例输出:

✅ 参考答案:
📖 参考代码
n = int(input())
# 初始化第1年的数据(索引从1开始)
m0 = [0] * (n + 1)  # 新学徒(0年经验)
m1 = [0] * (n + 1)  # 1年经验
m2 = [0] * (n + 1)  # 2年经验
m3 = [0] * (n + 1)  # 3年及以上经验(师傅)
# 第1年:只有1位创始师傅
m0[1] = 0
m1[1] = 0
m2[1] = 0
m3[1] = 1
# 递推计算第2年到第n年
for year in range(2, n + 1):
    m0[year] = m3[year - 1]                   # 师傅培养新学徒
    m1[year] = m0[year - 1]                   # 新学徒变为1年学徒
    m2[year] = m1[year - 1]                   # 1年学徒变为2年学徒
    m3[year] = m3[year - 1] + m2[year - 1]    # 师傅保留 + 2年学徒晋升
# 计算总人数
total = m0[n] + m1[n] + m2[n] + m3[n]
print(total)
第 12 题 编程操作题 15 分

题目描述:
风筝制作是国家级非物质文化遗产。某风筝工坊准备制作一批风筝,工匠有三种材料库存:竹条、纸张、颜料。工坊有 n 种风筝设计方案,每种方案需要消耗一定数量的材料,并带来相应的"艺术评分"。每种方案最多制作 1 个风筝。请使用枚举算法,在材料总量允许的前提下,找出总艺术评分最高的方案组合。

输入格式:
- 第一行:四个整数,空格分隔,依次为 n(方案数量,1 <= n <= 12)以及三种材料的库存量 A、B、C
- 接下来 n 行:每行四个整数,空格分隔,依次为该方案需要的竹条数、纸张数、颜料数和艺术评分

输出格式:
- 第一行:最大总评分:{评分}
- 第二行:选择方案编号:{编号列表,空格分隔}(编号从1开始,按升序排列)
- 如果有多种方案组合得到相同的最大评分,输出编号列表字典序最小的那个

示例输入:

4 5 5 5
2 1 1 8
1 2 1 6
3 3 2 15
1 1 2 5

示例输出:

最大总评分:23
选择方案编号:1 3
样例说明:
- 库存:竹条5、纸张5、颜料5
- 方案1:竹2纸1颜料1评8;方案2:竹1纸2颜料1评6;方案3:竹3纸3颜料2评15;方案4:竹1纸1颜料2评5
- 枚举所有 2^4=16 种组合。选择方案1+3:竹条2+3=5,纸张1+3=4,颜料1+2=3,均未超库存,总评分8+15=23(最优)
算法提示:枚举从 0 到 2^n-1 的所有整数,每个整数的二进制位表示对应方案是否被选中。检查每种组合的材料用量是否在库存范围内,更新最大评分。
✅ 参考答案:
📖 参考代码
# 读取输入
data = list(map(int, input().split()))
n, stock_a, stock_b, stock_c = data[0], data[1], data[2], data[3]
plans = []
for i in range(n):
    a, b, c, score = map(int, input().split())
    plans.append((a, b, c, score))
max_score = -1
best_combo = []
# 枚举所有方案组合(2^n 种)
for mask in range(1 << n):
    total_a = 0
    total_b = 0
    total_c = 0
    total_score = 0
    selected = []
    # 检查每位是否被选中
    for i in range(n):
        if mask & (1 << i):
            a, b, c, score = plans[i]
            total_a += a
            total_b += b
            total_c += c
            total_score += score
            selected.append(i + 1)
    # 检查材料是否足够
    if total_a <= stock_a and total_b <= stock_b and total_c <= stock_c:
        if total_score > max_score:
            max_score = total_score
            best_combo = selected
# 输出结果
print(f"最大总评分:{max_score}")
print(f"选择方案编号:{' '.join(map(str, best_combo))}")
第 13 题 编程操作题 20 分

题目描述:
木雕艺术是中华民族的传统手工艺。一位木雕艺人创作了 n 块风格各异的木雕作品,编号为 1 到 n,每块木雕有一个"风格值",其数值即为其编号(即木雕 i 的风格值为 i)。现要将这 n 块木雕排列成一行展示,要求相邻两块木雕的风格值之和必须为质数。
请使用DFS回溯算法,找出所有满足条件的排列方案,并输出方案总数和所有方案(按字典序升序排列)。

输入格式:

输出格式:
- 第一行:共{方案数}种排列方案
- 接下来每行输出一种方案,格式为数字空格分隔(如 1 4 3 2 5)
- 方案按字典序升序排列

示例输入:

5

示例输出:

共4种排列方案
1 4 3 2 5
3 4 1 2 5
5 2 1 4 3
5 2 3 4 1
样例说明:
对于 n=5,风格值为 1~5。检查相邻之和是否为质数:
- 方案 "1 4 3 2 5":1+4=5(质)、4+3=7(质)、3+2=5(质)、2+5=7(质),全部满足
- 方案 "3 4 1 2 5":3+4=7(质)、4+1=5(质)、1+2=3(质)、2+5=7(质),全部满足
- 方案 "5 2 1 4 3":5+2=7(质)、2+1=3(质)、1+4=5(质)、4+3=7(质),全部满足
- 方案 "5 2 3 4 1":5+2=7(质)、2+3=5(质)、3+4=7(质)、4+1=5(质),全部满足
共4种方案,按字典序升序排列。
算法提示:
1. 预处理:先生成所有 <= 2n 的质数集合,便于快速判断
2. DFS回溯:使用 used[] 数组标记哪些数字已被使用,path 列表存储当前排列
3. 递归终止条件:path 长度等于 n 时,记录一个有效方案
4. 剪枝:在选择下一个数字时,检查它与 path 中最后一个数字的和是否为质数
✅ 参考答案:
📖 参考代码
n = int(input())
# 预处理:生成 2 到 2n 范围内的质数集合
def is_prime(x):
    if x < 2:
        return False
    for i in range(2, int(x ** 0.5) + 1):
        if x % i == 0:
            return False
    return True
primes = set()
for num in range(2, 2 * n + 1):
    if is_prime(num):
        primes.add(num)
# 回溯搜索
solutions = []           # 存储所有有效方案
used = [False] * (n + 1)  # used[i] 表示数字 i 是否已被使用
def backtrack(path):
    # 终止条件:已排列完所有 n 块木雕
    if len(path) == n:
        solutions.append(path[:])  # 保存当前排列的副本
        return
    # 尝试每个未使用的数字
    for i in range(1, n + 1):
        if used[i]:
            continue
        # 剪枝:如果 path 非空,检查与最后一个数字的和是否为质数
        if len(path) > 0:
            if (path[-1] + i) not in primes:
                continue
        # 选择数字 i
        used[i] = True
        path.append(i)
        # 递归搜索
        backtrack(path)
        # 回溯:撤销选择
        path.pop()
        used[i] = False
# 启动回溯
backtrack([])
# 输出结果
print(f"共{len(solutions)}种排列方案")
for sol in solutions:
    print(' '.join(map(str, sol)))
第 14 题 编程操作题 20 分

题目描述:
某非遗文化节设有 m 个互动体验摊位,编号为 1 到 m。第 i 号摊位的体验时长为 t[i] 分钟(体验一旦开始不可中断)。每个摊位同一时间只能容纳 1 位游客体验。
共有 n 位报名游客,按报名顺序编号为 1 到 n。游客 j 在时刻 arrive[j] 分钟到达文化节现场,希望体验编号为 want[j] 的摊位。
请编写模拟程序,模拟文化节的运行过程,规则如下:
1. 游客到达后,如果其心仪的摊位处于空闲状态,且该摊位的等待队列为空,则立即开始体验
2. 游客到达后,如果其心仪的摊位正被占用或等待队列非空,则该游客加入该摊位的等待队列(按到达时间先后排队)
3. 当摊位完成一次体验时,立即检查等待队列:如果队列非空,选择队列中最早到达的游客开始体验;如果队列为空,摊位恢复空闲状态
4. 所有游客都不会中途离开,最终所有游客都会完成体验

输入格式:
- 第一行:两个整数 m 和 n(1 <= m <= 10, 1 <= n <= 100),空格分隔
- 第二行:m 个整数,表示每个摊位的体验时长(单位:分钟),空格分隔
- 接下来 n 行:每行两个整数,表示游客的到达时刻 arrive[j] 和心仪摊位编号 want[j],空格分隔。到达时刻按非递减顺序给出(即 arrive[1] <= arrive[2] <= ... <= arrive[n])

输出格式:
- 每位游客一行信息:游客{j}:到达{到达时刻} 摊位{摊位号} 开始{开始时刻} 结束{结束时刻}(j 从1开始)
- 一个空行
- 每个摊位一行:摊位{i}服务人次:{人次}(i 从1开始)

示例输入:

2 4
10 15
0 1
3 1
5 2
8 1

示例输出:

游客1:到达0 摊位1 开始0 结束10
游客2:到达3 摊位1 开始10 结束20
游客3:到达5 摊位2 开始5 结束20
游客4:到达8 摊位1 开始20 结束30
摊位1服务人次:3
摊位2服务人次:1
样例说明:
- 时刻0:游客1到达,1号摊位空闲且队列为空,开始体验,结束于0+10=10
- 时刻3:游客2到达,1号摊位被占用(游客1在10结束),加入1号等待队列:[2]
- 时刻5:游客3到达,2号摊位空闲且队列为空,开始体验,结束于5+15=20
- 时刻8:游客4到达,1号摊位被占用,加入1号等待队列:[2, 4]
- 时刻10:1号摊位完成游客1,从等待队列取游客2开始体验,结束于10+10=20
- 时刻20:1号摊位完成游客2,从等待队列取游客4开始体验,结束于20+10=30
- 时刻20:2号摊位完成游客3,等待队列为空,恢复空闲
- 时刻30:1号摊位完成游客4,等待队列为空,恢复空闲
✅ 参考答案:
📖 参考代码
# 读取输入
m, n = map(int, input().split())
times = list(map(int, input().split()))  # 各摊位体验时长
arrive = []
want = []
for j in range(n):
    a, w = map(int, input().split())
    arrive.append(a)
    want.append(w)
# 初始化:每个摊位的下一个空闲时间和等待队列
# 使用 1-indexed 方便处理
free_time = [0] * (m + 1)
wait_queue = [[] for _ in range(m + 1)]  # 每个元素为 (游客编号, 到达时刻)
# 存储每位游客的开始和结束时间
start_time = [0] * (n + 1)
end_time = [0] * (n + 1)
# 模拟主循环:按到达顺序处理每位游客
for j in range(n):
    arr_j = arrive[j]
    booth = want[j]
    # 关键步骤:先处理该摊位的等待队列
    # 如果摊位在游客到达前已空闲且队列中有等待者,先让他们开始体验
    while wait_queue[booth]:
        # 队列不为空,且摊位已空闲(空闲时间 <= 当前到达时刻)
        if free_time[booth] <= arr_j:
            guest_id, guest_arr = wait_queue[booth].pop(0)
            start_t = max(guest_arr, free_time[booth])
            start_time[guest_id] = start_t
            end_time[guest_id] = start_t + times[booth - 1]
            free_time[booth] = end_time[guest_id]
        else:
            # 摊位还未空闲,等下次再处理
            break
    # 现在处理当前到达的游客
    if free_time[booth] <= arr_j and len(wait_queue[booth]) == 0:
        # 摊位空闲且队列为空 → 立即开始体验
        start_time[j + 1] = arr_j
        end_time[j + 1] = arr_j + times[booth - 1]
        free_time[booth] = end_time[j + 1]
    else:
        # 摊位被占用或队列非空 → 加入等待队列
        wait_queue[booth].append((j + 1, arr_j))
# 处理所有摊位剩余的等待队列
for i in range(1, m + 1):
    while wait_queue[i]:
        guest_id, guest_arr = wait_queue[i].pop(0)
        start_t = max(guest_arr, free_time[i])
        start_time[guest_id] = start_t
        end_time[guest_id] = start_t + times[i - 1]
        free_time[i] = end_time[guest_id]
# 统计每个摊位的服务人次
service_count = [0] * (m + 1)
for j in range(n):
    service_count[want[j]] += 1
# 输出游客信息
for j in range(1, n + 1):
    print(f"游客{j}:到达{arrive[j-1]} 摊位{want[j-1]} 开始{start_time[j]} 结束{end_time[j]}")
print()
# 输出摊位服务人次
for i in range(1, m + 1):
    print(f"摊位{i}服务人次:{service_count[i]}")