备战蓝桥杯国赛【Day 16】

📌 写在前面 :今天的3道题全部来自第十四届蓝桥杯省赛/国赛真题 ,核心考点包括:DFS枚举门电路组合、Lucas定理优化异或金字塔、贪心策略处理异或消除。每道题我都会从暴力思路讲起,一步步推导到最优解,讲清楚"为什么能这样优化"。


📚 今日刷题清单

题号 题目 来源 类型 难度 核心考点
1 与或异或 蓝桥杯 DFS枚举 ⭐⭐⭐ 递归回溯、位运算、剪枝
2 异或金字塔 蓝桥杯 组合数学 ⭐⭐⭐⭐⭐ Lucas定理、组合数奇偶性、位运算
3 小小蓝的异或消除 蓝桥杯 贪心+堆 ⭐⭐⭐⭐ 优先队列、异或性质、贪心策略

一、与或异或 ⭐⭐⭐ DFS枚举+位运算

1.1 题目描述

小蓝有一张门电路的逻辑图,呈金字塔结构:

  • 第0层(顶层输入):In[0..4],共5个值
  • 第1层:4个门电路,每个接收上一层相邻两个值
  • 第2层:3个门电路
  • 第3层:2个门电路
  • 第4层(输出层):1个门电路,输出 Out

每个门电路可以是与门(&)、或门(|)、异或门(^) 中的一种。

已知输入为 In = [1, 0, 1, 0, 1],求有多少种不同的门电路组合方式,使得最终输出 Out = 1

1.2 关键思路:DFS枚举

暴力思路 :每个门有3种选择,共 3^10 个门(第1层4个+第2层3个+第3层2个+第4层1个=10个),总组合数 3^10 = 59049,完全可以枚举!

DFS设计

  • arr[i][j] 表示第 i 行第 j 个节点的值
  • arr[0] 是输入层,已知
  • arr[1][0] 开始,逐个确定每个门的类型和输出值
  • 当填到 arr[4][0](输出层)时,检查是否为1

递归顺序

  • 当前处理 arr[x][y],它由 arr[x-1][y]arr[x-1][y+1] 运算得到
  • 处理完 arr[x][y] 后:
    • 如果同行还有元素(x+y < 4),处理 arr[x][y+1]
    • 否则进入下一行,处理 arr[x+1][0]

1.3 推演验证

复制代码
输入: In = [1,0,1,0,1]

金字塔结构:
第0层: 1   0   1   0   1
       ↘↙ ↘↙ ↘↙ ↘↙
第1层:  ?   ?   ?   ?
       ↘↙ ↘↙ ↘↙
第2层:   ?   ?   ?
       ↘↙ ↘↙
第3层:    ?   ?
       ↘↙
第4层:     ? (Out)

DFS过程:
从arr[1][0]开始,尝试3种运算:
- &: 1&0 = 0
- |: 1|0 = 1  
- ^: 1^0 = 1

假设选|,arr[1][0]=1,继续处理arr[1][1](因为1+0<4)
arr[1][1]由arr[0][1]=0和arr[0][2]=1运算...
...
直到arr[4][0],检查是否为1,统计合法方案数

1.4 题解

python 复制代码
# 初始化金字塔结构
# arr[i][j] 表示第i行第j列的值
# 第0行有5个元素,第1行4个,...,第4行1个
arr = [[0] * (5 - i) for i in range(5)]
arr[0] = [1, 0, 1, 0, 1]  # 输入层

ans = 0  # 合法方案数

def dfs(x, y):
    """
    x: 当前行号 (1~4)
    y: 当前列号 (0~4-x)
    """
    global ans, arr
    
    # 到达输出层(第4行),检查输出是否为1
    if x == 5:
        if arr[4][0] == 1:
            ans += 1
        return
    
    # 尝试三种门电路:与(0)、或(1)、异或(2)
    for op in range(3):
        if op == 0:
            # 与门
            arr[x][y] = arr[x-1][y] & arr[x-1][y+1]
        elif op == 1:
            # 或门
            arr[x][y] = arr[x-1][y] | arr[x-1][y+1]
        else:
            # 异或门
            arr[x][y] = arr[x-1][y] ^ arr[x-1][y+1]
        
        # 决定下一个处理位置
        if x + y < 4:
            # 当前行还有未处理的元素,向右移动
            dfs(x, y + 1)
        else:
            # 当前行处理完毕,进入下一行
            dfs(x + 1, 0)

# 从第1行第0列开始DFS
dfs(1, 0)
print(ans)

复杂度 :时间 O(3^10) = O(59049),空间 O(5×5)(递归栈深度最多5层)

1.5 关键细节

坑点 说明
递归终止条件 x == 5 表示已经填完第4行(输出层),不是 x == 4
下一个位置判断 x + y < 4:第 x 行有 5-x 个元素,列号范围 04-x,所以最后一个元素的 y = 4-x,此时 x+y = 4。如果 x+y < 4,说明还没到最后一个
全局变量 ansarr 要用 global 声明,或者改用类封装
运算优先级 &、`

二、异或金字塔 ⭐⭐⭐⭐⭐ 组合数学+Lucas定理

2.1 题目描述

卓儿有一个异或金字塔:

  • 第1层(底层):a[0], a[1], ..., a[n-1]
  • 第2层:a[0]^a[1], a[1]^a[2], ..., a[n-2]^a[n-1]
  • 第3层:第2层相邻两个异或
  • ...
  • 第n层:只剩一个数,即答案

给定底层数组,求最顶部的数字。

数据范围n ≤ 10^5

2.2 关键思路:组合数+Lucas定理

暴力思路 :模拟金字塔构建,逐层计算。时间 O(n²),空间 O(n²)
超时原因n=10^5 时,n² = 10^10,完全不可接受。

核心观察 :顶层结果 = 底层元素的线性组合(异或意义下)

复制代码
第1层: a0, a1, a2, a3, ...
第2层: a0^a1, a1^a2, a2^a3, ...
第3层: (a0^a1)^(a1^a2)=a0^a2, (a1^a2)^(a2^a3)=a1^a3, ...
第4层: (a0^a2)^(a1^a3)=a0^a1^a2^a3, ...

规律 :第 k 层的每个元素,是底层某些元素的异或,系数由组合数决定!

具体地,顶层结果 = Σ C(n-1, i) * a[i](异或意义下,即系数模2)

异或的系数是0或1 :只有当 C(n-1, i)奇数 时,a[i] 才会出现在结果中。

Lucas定理C(m, k) 为奇数,当且仅当 (k & m) == k(即 k 的二进制位是 m 的二进制位的子集)

2.3 数学推导

Lucas定理回顾

对于质数 p,有:
C(m, k) ≡ Π C(m_i, k_i) (mod p)

其中 m_i, k_im, kp 进制表示。

p=2 时:

  • m, k 的二进制表示为 m = (m_t...m_1m_0)_2, k = (k_t...k_1k_0)_2
  • C(m, k) ≡ Π C(m_i, k_i) (mod 2)
  • C(0,0)=1, C(1,0)=1, C(1,1)=1, C(0,1)=0
  • 所以 C(m_i, k_i) = 0 当且仅当 m_i=0k_i=1

因此:C(m, k) 为奇数 ⟺ 对所有 i,不存在 m_i=0, k_i=1k 的二进制位是 m 的二进制位的子集 ⟺ (k & m) == k

2.4 推演验证

复制代码
输入: n=8, a=[2,10,5,12,9,5,1,5]

mask = n-1 = 7 = 111₂

检查每个i:
i=0: 000 & 111 = 000 == 0 ✅ → a[0]=2
i=1: 001 & 111 = 001 == 1 ✅ → a[1]=10
i=2: 010 & 111 = 010 == 2 ✅ → a[2]=5
i=3: 011 & 111 = 011 == 3 ✅ → a[3]=12
i=4: 100 & 111 = 100 == 4 ✅ → a[4]=9
i=5: 101 & 111 = 101 == 5 ✅ → a[5]=5
i=6: 110 & 111 = 110 == 6 ✅ → a[6]=1
i=7: 111 & 111 = 111 == 7 ✅ → a[7]=5

result = 2^10^5^12^9^5^1^5 = 9 ✓

为什么n=8时所有i都满足?

因为 n-1=7=111₂,而 i 最大是 7=111₂,所以 i 的所有二进制位都不会超过 7 的位,(i & 7) == i 恒成立。

反例n=6mask=5=101₂

  • i=2=010₂: 010 & 101 = 000 != 2
  • i=3=011₂: 011 & 101 = 001 != 3

所以 a[2]a[3] 不会出现在结果中。

2.5 题解

python 复制代码
n = int(input())
a = list(map(int, input().split()))

result = 0
mask = n - 1  # 对应 Lucas 定理中的 m = n-1

for i in range(n):
    # Lucas定理:C(n-1, i) 为奇数 ⟺ (i & (n-1)) == i
    if (i & mask) == i:
        result ^= a[i]  # 系数为1,异或进结果

print(result)

复杂度 :时间 O(n),空间 O(1)(除输入数组外)

2.6 关键细节

坑点 说明
Lucas定理条件 C(m,k) 为奇数 ⟺ (k & m) == k,这里 m = n-1
mask的设定 mask = n - 1,不是 n。因为金字塔有 n 层,从底层到顶层经过 n-1 次运算
位运算优先级 i & mask 的括号可以省略(& 优先级高于 ==),但加上更清晰
为什么O(n)能过 利用了组合数学性质,避免了 O(n²) 的模拟
适用范围 只有当运算满足结合律交换律(如异或、加法模2)时才适用

2.7 本质理解

复制代码
异或金字塔的递推,本质上是在做:
result = Σ a[i] * C(n-1, i)  (mod 2)

因为:
- 异或 = 加法模2
- 每个a[i]出现的次数 = 从底层到顶部的路径数 = C(n-1, i)
- 模2后,只有奇数次出现才保留

这个思想可以推广到其他满足结合律的运算!


三、小小蓝的异或消除 ⭐⭐⭐⭐ 贪心+优先队列

3.1 题目描述

对数组 arr 执行"异或消除"操作:

  1. 找到数组中的最大值 a次大值 b(若有多个相同的最大值或次大值,取最左边的那个)
  2. ab 同时移除
  3. 计算 c = a ^ b
  4. c 放入数组的最左边

不断操作,直到数组只剩一个元素,输出该元素。

数据范围n ≤ 10^5

3.2 关键思路:贪心+最大堆

暴力思路 :每次找最大和次大(O(n)),移除并插入(O(n)),总复杂度 O(n²)
超时原因n=10^5 时,n² = 10^10,不可接受。

优化思路------最大堆(优先队列)

  • 用最大堆维护数组元素,找最大和次大变为 O(log n)
  • 移除和插入也是 O(log n)
  • 总复杂度 O(n log n)

但这里有个关键问题:"最左边的"这个条件怎么处理?

重新审视"最左边"

  • 如果最大值只有一个,那它就是最左边的最大值
  • 如果最大值有多个,取最左边的那个
  • 次大值同理

堆的实现难点:普通堆无法快速找到"最左边的"元素。

换个思路 :如果值相同,下标小的优先级高。堆中存储 (值, 下标),按值降序、下标升序排序。

但移除两个元素后,其他元素的下标会变化! 这导致"最左边"的条件很难维护。

关键观察:题目说"将c放入数组的最左边",这意味着:

  • 新元素 c 的下标比所有现有元素都小
  • 或者,我们可以把数组看作一个双端队列,从左边弹出、左边插入?

重新理解操作

  • "最左边"指的是当前数组的最左边
  • 移除 ab 后,数组长度减2
  • 插入 c 到最左边,数组长度减1(总体)

如果不用维护具体下标,只用保证"值相同时先取先出现的"

  • 可以用堆,但需要标记删除
  • 或者用 TreeMap / SortedList 维护有序集合

实际上,更简单的做法

  • 观察发现:这个操作的本质是不断将两个最大数异或后放回
  • 如果数组长度为奇数,最后剩1个;为偶数,最后剩... 题目保证最后剩1个?

等等,重新看样例

复制代码
输入: [3, 5, 7, 5, 3]

第1轮: 最大=7(下标2), 次大=5(下标1, 因为5有两个,取最左边的下标1)
       移除7和5(下标1),剩余[3, 5, 3](注意:移除下标1的5和下标2的7后,数组变成[3,5,3]?)
       等等,原数组是[3,5,7,5,3],移除下标1的5和下标2的7,剩余[3,5,3]
       c = 7^5 = 2,放入最左边 → [2, 3, 5, 3]

不对,剩余应该是[3,5,3],然后插入2到最左边 → [2,3,5,3]?长度变成4了?

但题目说"将a和b同时移除",所以长度减2,然后插入c,长度减1。
原长度5,操作后长度4。

继续:
第2轮: [2,3,5,3],最大=5(下标2), 次大=3(下标1, 因为3有两个,取最左边的下标1)
       移除5和3(下标1),剩余[2,3]
       c = 5^3 = 6,放入最左边 → [6,2,3]?长度3?

等等,剩余[2,3],插入6 → [6,2,3],长度3。

第3轮: [6,2,3],最大=6(下标0), 次大=3(下标2)
       移除6和3,剩余[2]
       c = 6^3 = 5,放入最左边 → [5,2]?长度2?

第4轮: [5,2],最大=5(下标0), 次大=2(下标1)
       移除5和2,剩余[]
       c = 5^2 = 7,放入最左边 → [7]

输出7 ✓

关键发现 :数组长度每次减1(移除2个,插入1个)。从 n 减到1,需要 n-1 次操作。

关于"最左边"的实现

  • 如果用堆,需要知道每个值的下标
  • 但下标会动态变化,很难维护

换个角度:如果所有元素都不同,那"最左边"就没意义了,直接取最大和次大。

  • 如果有重复元素,需要区分哪个是先出现的

实际上,可以用堆+延迟删除

  • 堆中存 (值, 原始下标)
  • 每次取堆顶两个元素,检查它们是否还在数组中(用布尔数组标记)
  • 如果不在,重新取

但"最左边"要求:值相同时,原始下标小的优先。堆的排序规则:值降序,值相同时下标升序。

然而,移除两个元素后,其他元素的下标会左移,这会影响"最左边"的判断!

重新审视 :也许"最左边"只在值相同时有影响。如果值都不同,那下标无所谓。

关键观察 :异或操作 a ^ b 的结果,与 ab 的位置无关,只与值有关。

大胆猜想:也许"最左边"这个条件,在最终结果上并不影响?或者可以用某种方式规避?

实际上,如果仔细看操作 :移除 ab 后,数组会收缩,但插入 c 到最左边。这意味着:

  • 新元素 c 的下标是0
  • 其他元素的下标不变?还是左移?

题目说"将c放入数组的最左边",这意味着 c 成为新的下标0,其他元素右移 ?还是 c 插入到最前面,其他元素后移?

如果是链表 结构,插入到头部是 O(1)。但找最大和次大需要 O(n)

综合考虑 :用 SortedList (或 TreeMap)维护有序集合,同时用链表维护实际顺序?

这太复杂了。也许题目数据保证元素各不相同?或者 n 比较小?

看代码 :用户只给了一行代码 result ^= int(x),这是对所有输入数字异或!

惊人发现result = a[0] ^ a[1] ^ ... ^ a[n-1],即所有元素的异或和

验证样例3^5^7^5^3 = 3^3^5^5^7 = 0^0^7 = 7

为什么?

3.3 数学证明

关键性质a ^ a = 0a ^ 0 = a,异或满足交换律结合律

观察操作:移除 ab,插入 a ^ b

整个数组所有元素的异或和

  • 操作前:X = ... ^ a ^ b ^ ...
  • 操作后:X' = ... ^ (a ^ b) ^ ...(因为 ab 被移除,替换成 a^b

等等,这不对。数组长度变了,元素也变了。

换个思路 :考虑所有出现过的数字的异或和(包括被移除的)

每次操作:

  • 移除 ab,它们不再出现在数组中
  • 加入 c = a ^ b

S 为数组中所有元素的异或和:

  • 操作前:S = a ^ b ^ rest
  • 操作后:S' = c ^ rest = (a ^ b) ^ rest

所以 S' = S?不对,ab 被移除了,但 c 加入了。

实际上:S' = (S ^ a ^ b) ^ c = S ^ a ^ b ^ (a ^ b) = S ^ 0 = S

异或和不变!

最终答案 = 初始数组的异或和!

因为最后只剩一个元素,它的值就是整个数组的异或和。

3.4 题解

python 复制代码
import sys

# 读取所有数字,直接异或
result = 0
for x in sys.stdin.readline().split():
    result ^= int(x)

print(result)

或者更简洁:

python 复制代码
print(eval('^'.join(sys.stdin.readline().split())))

复杂度 :时间 O(n),空间 O(1)

3.5 关键细节

坑点 说明
异或和不变性 操作前后,整个数组的异或和保持不变。这是解题关键
"最左边"条件 虽然题目说了"最左边",但实际上不影响最终异或和。因为异或满足交换律,顺序无关
输入读取 第二行有 n 个数字,可以用 sys.stdin.readline().split() 一次性读取
初始异或和 最后剩下的元素 = 初始所有元素的异或和
证明严谨性 S' = S ^ a ^ b ^ (a^b) = S,因为 a ^ a = 0, b ^ b = 0

3.6 为什么"最左边"不影响结果

复制代码
假设数组为 [x1, x2, ..., xk, a, ..., b, ...](a和b是要移除的两个元素)

无论a和b在什么位置,移除它们并插入a^b后:
- 新数组的异或和 = (原异或和 ^ a ^ b) ^ (a ^ b) = 原异或和

因为异或满足交换律和结合律,元素的顺序和位置不影响总异或和。

所以"最左边"这个条件只是干扰项,不影响最终答案!


🎯 今日复盘总结

题目 核心技巧 思维路径 易错点 国赛价值
与或异或 DFS枚举 小规模→直接枚举所有组合→统计合法方案 递归终止条件、下一个位置判断 ⭐⭐⭐ 基础回溯
异或金字塔 Lucas定理+组合数学 模拟找规律→发现组合数系数→Lucas定理优化→O(n) mask=n-1、位运算判断 ⭐⭐⭐⭐⭐ 神级优化
异或消除 异或不变性 模拟操作→发现异或和不变→直接算初始异或和 "最左边"是干扰条件、输入读取 ⭐⭐⭐⭐ 思维转化

记得 点赞收藏,算法路上不迷路!有问题评论区见 👇

相关推荐
世辰辰辰6 小时前
批量修改图片/文本名子
开发语言·python·批量修改文件名
myenjoy_17 小时前
MQTT 与 Sparkplug B——从车间到云端的最后一公里
网络·python
颜酱9 小时前
LangChain 输出解析器:把模型回复变成你要的数据
python·langchain
2401_873479409 小时前
企业安全运营中,如何用IP离线库提前发现失陷主机?三步实现风险画像
网络·数据库·python·tcp/ip·ip
weixin_523185329 小时前
Java基础知识总结(四):引用数据类型与参数传递机制
java·开发语言·python
码农飞哥10 小时前
我把RAG召回率从60%提到90%,就改了这两件事
python·知识库·向量检索·rag·效果提示
宸津-代码粉碎机10 小时前
Spring AI企业级实战|从RAG优化到Agent多工具调度
java·大数据·人工智能·后端·python·spring
yuhuofei202110 小时前
【Python入门】Python中的字典dict
python
Jinkxs10 小时前
Python基础 - 文件的写入操作 write与writelines方法
android·服务器·python
初学Python的小明10 小时前
Python格式化输出、运算符、分支&循环
python