📌 写在前面 :今天的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 个元素,列号范围 0 到 4-x,所以最后一个元素的 y = 4-x,此时 x+y = 4。如果 x+y < 4,说明还没到最后一个 |
| 全局变量 | ans 和 arr 要用 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_i 是 m, k 的 p 进制表示。
当 p=2 时:
m, k的二进制表示为m = (m_t...m_1m_0)_2,k = (k_t...k_1k_0)_2C(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=0且k_i=1
因此:C(m, k) 为奇数 ⟺ 对所有 i,不存在 m_i=0, k_i=1 ⟺ k 的二进制位是 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=6,mask=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 执行"异或消除"操作:
- 找到数组中的最大值
a和次大值b(若有多个相同的最大值或次大值,取最左边的那个) - 将
a和b同时移除 - 计算
c = a ^ b - 将
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的下标比所有现有元素都小 - 或者,我们可以把数组看作一个双端队列,从左边弹出、左边插入?
重新理解操作:
- "最左边"指的是当前数组的最左边
- 移除
a和b后,数组长度减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 的结果,与 a 和 b 的位置无关,只与值有关。
大胆猜想:也许"最左边"这个条件,在最终结果上并不影响?或者可以用某种方式规避?
实际上,如果仔细看操作 :移除 a 和 b 后,数组会收缩,但插入 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 = 0,a ^ 0 = a,异或满足交换律 和结合律
观察操作:移除 a 和 b,插入 a ^ b
整个数组所有元素的异或和:
- 操作前:
X = ... ^ a ^ b ^ ... - 操作后:
X' = ... ^ (a ^ b) ^ ...(因为a和b被移除,替换成a^b)
等等,这不对。数组长度变了,元素也变了。
换个思路 :考虑所有出现过的数字的异或和(包括被移除的)
每次操作:
- 移除
a和b,它们不再出现在数组中 - 加入
c = a ^ b
设 S 为数组中所有元素的异或和:
- 操作前:
S = a ^ b ^ rest - 操作后:
S' = c ^ rest = (a ^ b) ^ rest
所以 S' = S?不对,a 和 b 被移除了,但 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、位运算判断 | ⭐⭐⭐⭐⭐ 神级优化 |
| 异或消除 | 异或不变性 | 模拟操作→发现异或和不变→直接算初始异或和 | "最左边"是干扰条件、输入读取 | ⭐⭐⭐⭐ 思维转化 |
记得 点赞收藏,算法路上不迷路!有问题评论区见 👇