☰
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:位运算实战——二进制权限与最大异或解析
2026/10/27 8:22:10 网站建设 项目流程

一、引言

进入十月下旬,青少儿编程赛事进入密集备考期:

  • 第十八届蓝桥杯软件赛(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)。

所有操作结束后,输出:

  1. 拥有"管理员权限"(定义为第30位为 1)的用户数量;
  2. 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. 六种位运算符:与&、或|、异或^、取反~、左移<<、右移>>。
  2. 置位 / 清位 / 取位:
  3. 置 1:x |= (1 << k)
  4. 清 0:x &= ~(1 << k)
  5. 取第 k 位:(x >> k) & 1
  6. 异或的三大性质:
  7. 自反性:a ^ a = 0,a ^ 0 = a;
  8. 交换律 / 结合律:a ^ b ^ c = a ^ (b ^ c);
  9. 可用于"出现奇数次的数""不进位加法"。
  10. 二进制位贪心:要让x xor y尽量大,应从最高位开始,每一位尽量让结果为 1。
  11. 01-Trie(字典树):把每个数的二进制位当作一条从高位到低位走出的路径存进 Trie,查询时贪心地走"相反位",即可在O(log V)内求出与x异或最大的数。

四、解法 / 拆解

项目 A:位运算直接建模

每个用户的权限就是一个整数,用上面三板斧即可,无需任何复杂结构。

#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; }
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)数对,取异或最大值。思路直白、零模板,适合电子学会考级与初期练习。

#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; }
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),走不通才走相同位。

#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; }
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-TrieO(n·B)O(n·B)

六、易错点(七条,考试常踩)

  1. 运算符优先级:&、|、^的优先级低于==、<、+。写if ((x >> k) & 1 == 1)会先算1 == 1再&,必须加括号:if (((x >> k) & 1) == 1)。
  2. &=/|=与=混用:清位要用x &= ~(1 << k),漏掉&写成x = ~ (1 << k)会把其它位全清掉。
  3. 移位越界溢出:1 << k在 C++ 中是int,当k ≥ 31时左移溢出为 0 或负数。位宽可能到 31 位时,请写成1LL << k(用long long存权限)。
  4. 取反~的符号:~x在 C++ 里是按补码取反(含符号位),所以"清某位"要配合&,不要单独用~x当"取反整个数"。
  5. popcount 的坑:手写好while (x) { cnt += x & 1; x >>= 1; }时,若x是有符号负数会死循环;权限用非负整数即可,或先转无符号。
  6. 异或查询前要先插入还是先查询:01-Trie 求"两不同数异或最大"时,必须先 query 再 insert自己,否则会和自己异或得到 0,且漏掉"和之前的数配对"的情况。
  7. Trie 必须对齐到固定高位:插入 / 查询都要从同一个最高位B开始逐位走,不能因为"这个数很小"就少走几位,否则不同长度的数会在错误的层面对齐,答案错误。

七、进阶

  1. lowbit与树状数组:lowbit(x) = x & (-x)取出最低位的 1,是树状数组(Fenwick Tree)的基石;蓝桥杯 / 考级中"统计逆序对""单点改区间查"常与之配合。
  2. 状态压缩枚举子集:n ≤ 20时,用1<<n个整数表示"选 / 不选每个元素"的所有组合,for (int s = 0; s < (1<<n); ++s)即可遍历全部子集;还可for (int sub = s; sub; sub = (sub-1) & s)枚举s的所有子集,是蓝桥杯经典技巧。
  3. 01-Trie 不止最大异或:把"任意两数异或最大值"升级为"区间内两数异或最大值"(可持久化 Trie),或"异或某个定值 k 后最大"(同样走相反位)。
  4. 异或找"出现奇数次的数":a[1] ^ a[2] ^ … ^ a[n],利用x ^ x = 0把所有成对出现的数消掉,只剩出现奇数次的那个;若只有一个数出现一次、其余都两次,结果是它就是它本身。
  5. 位运算加速小技巧:判断奇偶用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 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询