一、引言
进入十月下旬,青少儿编程赛事进入密集备考期:
- 第十八届蓝桥杯软件赛 (C/C++ 程序设计、Python 程序设计均为核心科目)报名已于 2026-10-08 10:00 开启,至 2026-12-18 14:00 截止 ,省赛 / 全国选拔赛定于 2027 年 4 月,全国总决赛 2027 年 6 月中上旬。
- 中国电子学会 2026 年 12 月青少年软件编程等级考试 (C/C++ 一级至十级、Python 一级至六级)报名 2026-09-23 --- 12-02 16:00,全国测试 12-10,统考 12-12(C/C++ 8:30--10:30、Python 14:00--15:00)。
- 全国青少年信息素养大赛算法应用赛项,大纲明确包含二进制、位运算与基础算法。
三个赛事共同高频出现一个"看上去小、实际很能拉开差距"的知识点:位运算 。蓝桥杯填空题常考 lowbit、异或性质;电子学会考级把位运算列为独立章节;信息素养大赛的算法题也常借二进制优化。今天我们用一道原创题,把位运算从"记六个符号"打通到"01-Trie 最大异或"的完整能力链。
二、题目 / 项目目标
我们把位运算拆成两个递进的小项目,都来自"校园科技节"的真实化场景。
项目 A(基础):社团权限徽章
有 m 个用户,初始权限均为 0(二进制每一位代表一类权限)。随后有 q 次操作:
add u k:给用户u增加第k类权限(把第k位置成 1);del u k:取消用户u的第k类权限(把第k位置成 0)。
所有操作结束后,输出:
- 拥有"管理员权限"(定义为第
30位为 1)的用户数量; m个整数,第i个为用户i权限中二进制1的个数(即其拥有的权限类别数)。
约束:m ≤ 1000,q ≤ 10^5,k ≤ 30。
项目 B(核心算法):异或谜题------最大异或数对
科技节颁奖台前挂着一串灯,编号为 a[1..n](均为非负整数)。主持人说:任选两盏不同的灯 ,把它们编号异或(XOR),能得到的最大结果是多少?
即:给定 n 个数 a[1..n],求 max(a[i] xor a[j])(i ≠ j)。
约束:n ≤ 2000,a[i] < 2^31。
三、核心考点
- 六种位运算符 :与
&、或|、异或^、取反~、左移<<、右移>>。 - 置位 / 清位 / 取位:
- 置 1:
x |= (1 << k) - 清 0:
x &= ~(1 << k) - 取第 k 位:
(x >> k) & 1 - 异或的三大性质:
- 自反性:
a ^ a = 0,a ^ 0 = a; - 交换律 / 结合律:
a ^ b ^ c = a ^ (b ^ c); - 可用于"出现奇数次的数""不进位加法"。
- 二进制位贪心 :要让
x xor y尽量大,应从最高位开始,每一位尽量让结果为 1。 - 01-Trie(字典树) :把每个数的二进制位当作一条从高位到低位走出的路径存进 Trie,查询时贪心地走"相反位",即可在
O(log V)内求出与x异或最大的数。
四、解法 / 拆解
项目 A:位运算直接建模
每个用户的权限就是一个整数,用上面三板斧即可,无需任何复杂结构。
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m, q;
if (!(cin >> m >> q)) return 0;
vector<int> p(m, 0);
const int ADMIN = 30; // 管理员权限位
while (q--) {
string op; int u, k;
cin >> op >> u >> k;
if (op == "add") p[u] |= (1 << k); // 置 1
else if (op == "del") p[u] &= ~(1 << k); // 清 0
}
int admin_cnt = 0;
for (int x : p) if ((x >> ADMIN) & 1) ++admin_cnt;
cout << admin_cnt << "\n";
for (int i = 0; i < m; ++i) {
if (i) cout << " ";
// 统计 1 的个数:GCC 内置函数;其他编译器可手写(见进阶)
cout << __builtin_popcount(p[i]);
}
cout << "\n";
return 0;
}
python
def main():
import sys
data = sys.stdin.read().split()
if not data: return
it = iter(data)
m = int(next(it)); q = int(next(it))
p = [0] * m
ADMIN = 30
for _ in range(q):
op = next(it); u = int(next(it)); k = int(next(it))
if op == "add":
p[u] |= (1 << k)
elif op == "del":
p[u] &= ~(1 << k)
admin_cnt = sum(1 for x in p if (x >> ADMIN) & 1)
ones = [bin(x).count("1") for x in p] # bin(x).count("1") 即 popcount
print(admin_cnt)
print(" ".join(map(str, ones)))
if __name__ == "__main__":
main()
复杂度 :时间 O(q + m),空间 O(m),完全可过 q = 10^5。
项目 B 解法一:暴力枚举(入门 / 考级首选)
直接枚举所有 (i, j) 数对,取异或最大值。思路直白、零模板,适合电子学会考级与初期练习。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
vector<int> a(n);
for (int i = 0; i < n; ++i) cin >> a[i];
int ans = 0;
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
ans = max(ans, a[i] ^ a[j]);
cout << ans << "\n";
return 0;
}
python
import sys
def main():
data = sys.stdin.read().split()
if not data: return
it = iter(data)
n = int(next(it))
a = [int(next(it)) for _ in range(n)]
ans = 0
for i in range(n):
for j in range(i + 1, n):
if a[i] ^ a[j] > ans:
ans = a[i] ^ a[j]
print(ans)
if __name__ == "__main__":
main()
复杂度 :时间 O(n²),空间 O(n)。n ≤ 2000 时约 200 万次异或,完全够用。
项目 B 解法二:01-Trie 贪心(蓝桥杯 / 竞赛优化)
当 n 大到 10^5、数值到 2^31 时,O(n²) 会超时。把每个数的二进制(高位到低位,共 31 位)当作一条路径插入 Trie;查询 x 时,从最高位起,每一步都优先走与当前位相反的分支(这样该位异或就是 1),走不通才走相同位。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int B = 30; // 处理到 2^30 量级,共 31 位(0..30)
struct Node { int ch[2]; Node() { ch[0] = ch[1] = 0; } };
vector<Node> trie(1); // 0 号节点为根
void insert(int x) {
int u = 0;
for (int i = B; i >= 0; --i) {
int b = (x >> i) & 1;
if (!trie[u].ch[b]) {
trie.push_back(Node());
trie[u].ch[b] = trie.size() - 1;
}
u = trie[u].ch[b];
}
}
int query(int x) {
int u = 0, res = 0;
for (int i = B; i >= 0; --i) {
int b = (x >> i) & 1;
int need = 1 - b; // 希望走相反位,使该位异或为 1
if (trie[u].ch[need]) { res |= (1 << i); u = trie[u].ch[need]; }
else { u = trie[u].ch[b]; }
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
vector<int> a(n);
for (int i = 0; i < n; ++i) cin >> a[i];
int ans = 0;
for (int x : a) {
// 先查询(只用已插入的数,避免 i==j),再插入自己
if (trie[0].ch[0] || trie[0].ch[1]) ans = max(ans, query(x));
insert(x);
}
cout << ans << "\n";
return 0;
}
python
class TrieNode:
__slots__ = ("ch",)
def __init__(self):
self.ch = [None, None] # ch[0], ch[1]
def insert(root, x, B=30):
node = root
for i in range(B, -1, -1):
b = (x >> i) & 1
if node.ch[b] is None:
node.ch[b] = TrieNode()
node = node.ch[b]
def query_max(root, x, B=30):
node, res = root, 0
for i in range(B, -1, -1):
b = (x >> i) & 1
need = 1 - b
if node.ch[need] is not None:
res |= (1 << i)
node = node.ch[need]
else:
node = node.ch[b]
return res
def max_xor_trie(a, B=30):
root = TrieNode()
ans = 0
for x in a:
# 先查询(只用已插入的数),再插入自己
if root.ch[0] is not None or root.ch[1] is not None:
cand = query_max(root, x, B)
if cand > ans: ans = cand
insert(root, x, B)
return ans
if __name__ == "__main__":
import sys
data = sys.stdin.read().split()
if data:
it = iter(data); n = int(next(it))
a = [int(next(it)) for _ in range(n)]
print(max_xor_trie(a))
复杂度 :插入 / 查询各 O(B),B = 31,总时间 O(n·B)、空间 O(n·B),n = 10^5 也轻松通过。
五、时间 / 空间复杂度小结
| 项目 | 解法 | 时间 | 空间 |
|---|---|---|---|
| A 权限徽章 | 位运算 | O(q + m) |
O(m) |
| B 最大异或 | 暴力枚举 | O(n²) |
O(n) |
| B 最大异或 | 01-Trie | O(n·B) |
O(n·B) |
六、易错点(七条,考试常踩)
- 运算符优先级 :
&、|、^的优先级低于==、<、+。写if ((x >> k) & 1 == 1)会先算1 == 1再&,必须加括号 :if (((x >> k) & 1) == 1)。 &=/|=与=混用 :清位要用x &= ~(1 << k),漏掉&写成x = ~ (1 << k)会把其它位全清掉。- 移位越界溢出 :
1 << k在 C++ 中是int,当k ≥ 31时左移溢出为 0 或负数。位宽可能到 31 位时,请写成1LL << k(用long long存权限)。 - 取反
~的符号 :~x在 C++ 里是按补码取反(含符号位),所以"清某位"要配合&,不要单独用~x当"取反整个数"。 - popcount 的坑 :手写好
while (x) { cnt += x & 1; x >>= 1; }时,若x是有符号负数会死循环;权限用非负整数即可,或先转无符号。 - 异或查询前要先插入还是先查询 :01-Trie 求"两不同数异或最大"时,必须先 query 再 insert 自己,否则会和自己异或得到 0,且漏掉"和之前的数配对"的情况。
- Trie 必须对齐到固定高位 :插入 / 查询都要从同一个最高位
B开始逐位走,不能因为"这个数很小"就少走几位,否则不同长度的数会在错误的层面对齐,答案错误。
七、进阶
lowbit与树状数组 :lowbit(x) = x & (-x)取出最低位的 1,是树状数组(Fenwick Tree)的基石;蓝桥杯 / 考级中"统计逆序对""单点改区间查"常与之配合。- 状态压缩枚举子集 :
n ≤ 20时,用1<<n个整数表示"选 / 不选每个元素"的所有组合,for (int s = 0; s < (1<<n); ++s)即可遍历全部子集;还可for (int sub = s; sub; sub = (sub-1) & s)枚举s的所有子集,是蓝桥杯经典技巧。 - 01-Trie 不止最大异或:把"任意两数异或最大值"升级为"区间内两数异或最大值"(可持久化 Trie),或"异或某个定值 k 后最大"(同样走相反位)。
- 异或找"出现奇数次的数" :
a[1] ^ a[2] ^ ... ^ a[n],利用x ^ x = 0把所有成对出现的数消掉,只剩出现奇数次的那个;若只有一个数出现一次、其余都两次,结果是它就是它本身。 - 位运算加速小技巧 :判断奇偶用
x & 1比x % 2快;取模 2 的幂用x & ((1<<k)-1)代替x % (1<<k);交换两数可用a ^= b; b ^= a; a ^= b;(注意a、b不能是同一变量,否则变 0)。
八、小结与互动
位运算不是"背六个符号",而是把问题搬到二进制层面重新观察:权限用一位表示、异或当成不进位加法、Trie 把"找最大异或"变成"高位贪心走相反路"。它是蓝桥杯填空题、电子学会考级独立章节、信息素养大赛算法题共同的高频考点,性价比极高。
今日互动 :你在做题时还踩过哪些位运算的坑?是优先级漏括号,还是
1 << 31溢出?欢迎在评论区留言,下一篇我们可以专门拆解"状态压缩 DP 入门"。
(本文为原创练习题,非任何赛事真题原题;代码均在 C++ / Python 双环境下可运行。)
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:
- 全国青少年信息素养大赛复赛集训题目Python&C++.docx
https://pan.quark.cn/s/93995d3cb150 - 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
https://pan.quark.cn/s/da97b5dbf75d - Python背记手册.pdf
https://pan.quark.cn/s/7568ae9ca92b - Python课程
https://pan.quark.cn/s/a94bf02d00c6 - 2024信息素养大赛图形化复赛集训题答案3-9
https://pan.quark.cn/s/6ccab7ec3cbc - 2025年03月份电子学会考级真题
https://pan.quark.cn/s/4403c4228912 - 2025全国青少年信息素养大赛赛项说明
https://pan.quark.cn/s/d9d0df4a9f29 - 青少儿信息素养大赛编程资料
https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注获取最新分享。