📌 写在前面 :位运算是算法竞赛中的"隐形加速器",它不仅能将 O ( n ) O(n) O(n) 优化到 O ( 1 ) O(1) O(1),更是状态压缩、树状数组等高级算法的基石。本文基于蓝桥杯官方课程,从二进制底层原理到竞赛实战,带你彻底掌握位运算!
一、位运算基础:二进制世界的六大法则
位运算直接对二进制位进行操作,是CPU最底层的运算方式,速度极快。
1.1 与运算 & ------ "有0则0,全1为1"
| x | y | x & y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
实例 :6 & 11 = 2
0110 (6)
& 1011 (11)
--------
0010 (2)
1.2 或运算 | ------ "有1则1,全0为0"
| x | y | x | y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
实例 :6 | 11 = 15
0110 (6)
| 1011 (11)
--------
1111 (15)
1.3 异或运算 ^ ------ "相同为0,不同为1" ⭐重点
| x | y | x ^ y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
核心性质(竞赛必考):
- 交换律 :
x ^ y = y ^ x - 结合律 :
x ^ (y ^ z) = (x ^ y) ^ z - 自反性 :
x ^ x = 0(自己异或自己等于0) - 零元素 :
x ^ 0 = x - 逆运算 :若
x ^ y = z,则z ^ y = x
实例 :6 ^ 11 = 13
0110 (6)
^ 1011 (11)
--------
1101 (13)
1.4 取反 ~ ------ "1变0,0变1"
实例 :~6 = -7(在32位系统中,注意符号位)
~ 0000 0110 (6)
--------
1111 1001 (-7, 补码表示)
💡 注意 :Python中整数无限精度,
~x = -x-1
1.5 左移 << ------ "乘以2的幂"
二进制位向左移动,右侧补0。每左移1位 = 乘以2
实例 :5 << 3 = 40
原数: 0000 0101 (5)
左移3位:0010 1000 (40)
# 相当于 5 * 2^3 = 40
1.6 右移 >> ------ "除以2的幂"
二进制位向右移动,左侧补符号位。每右移1位 = 除以2(向下取整)
实例 :13 >> 2 = 3
原数: 0000 1101 (13)
右移2位:0000 0011 (3)
# 相当于 13 // 4 = 3
二、位运算六大技巧:从入门到高手
技巧1:判断奇偶性 ⚡O(1)
python
# 传统方法
if x % 2 == 1: # 奇数
# 位运算优化:直接判断第0位
if x & 1: # 奇数(第0位为1)
原理:二进制第0位为1则奇数,为0则偶数。
技巧2:获取第 i 位
python
# 获取x的二进制第i位(从0开始)
bit = (x >> i) & 1
# 步骤拆解:
# 1. x >> i:将第i位移到第0位
# 2. & 1:提取第0位的值
技巧3:设置第 i 位为 1
python
x |= (1 << i)
# 原理:1 << i 生成只有第i位为1的数
# 或运算:有1则1,保证第i位变为1,其他位不变
技巧4:设置第 i 位为 0
python
x &= ~(1 << i)
# 原理:
# 1. 1 << i:生成第i位为1的数
# 2. ~(1 << i):取反,第i位变0,其他位变1
# 3. &运算:第i位强制变0,其他位保持不变
技巧5:判断是否为2的幂 ⭐高频考点
python
def is_power_of_two(x):
return x > 0 and (x & (x - 1)) == 0
# 原理:2的幂二进制只有1个1
# 例如:8 = 1000, 7 = 0111
# 8 & 7 = 0000 = 0 ✓
# 非2的幂:6 = 0110, 5 = 0101
# 6 & 5 = 0100 ≠ 0 ✗
技巧6:获取最低位的1(Lowbit)⭐树状数组核心
python
def lowbit(x):
return x & (-x)
# 原理:利用补码特性
# 例如:x = 12 = 1100
# -x = -12 = 补码(0100)
# 12 & (-12) = 0100 = 4
# 结果:提取出最低位的1及其后面的0
三、每日一题实战演练
📝 题目1:二进制中1的个数
题目链接 :蓝桥杯1331
题目描述 :给定整数 x x x,输出其二进制表示中1的个数。
输入 :32位整数 x x x(注意:包含负数!)
输出:1的个数
示例:
输入:9
输出:2
# 解释:9 = 1001,有2个1
解法一:逐位判断(通用,处理负数)
python
x = int(input())
ans = 0
for i in range(32): # 遍历32个二进制位
if (x >> i) & 1: # 获取第i位
ans += 1
print(ans)
复杂度 :时间 O ( 32 ) O(32) O(32),空间 O ( 1 ) O(1) O(1)
解法二:Lowbit优化(仅非负数)
python
def count_ones(x):
ans = 0
while x:
x -= x & (-x) # 每次消去最低位的1
ans += 1
return ans
# 原理:lowbit提取最低位的1,减去它就消去了这个1
# 循环次数 = 1的个数,效率更高
⚠️ 注意 :本题输入含负数,负数在Python中右移会无限填充1,因此必须使用32位遍历法!
📝 题目2:区间或
题目链接 :蓝桥杯3691
题目描述 :给定数组 a a a, q q q 次询问,每次求区间 [ l , r ] [l,r] [l,r] 的按位或结果。
关键思路:
- 或运算性质:只要有1就为1
- 区间或 = 对每一位单独计算,只要区间内该位至少有一个1,结果该位就是1
python
from itertools import accumulate
import sys
input = sys.stdin.readline
print = sys.stdout.write
n, q = map(int, input().split())
a = list(map(int, input().split()))
# 对每一位单独处理
a_bit = []
for i in range(31): # 遍历31个二进制位
now_bit = []
for x in a:
now_bit.append((x >> i) & 1) # 提取每个数第i位
# 前缀和:快速计算区间该位是否有1
a_bit.append(list(accumulate(now_bit)))
for _ in range(q):
l, r = map(int, input().split())
l -= 1; r -= 1 # 转0-indexed
ans = 0
for i in range(31):
if l == 0:
now = a_bit[i][r]
else:
now = a_bit[i][r] - a_bit[i][l - 1]
if now > 0: # 该位区间内有1
ans |= (1 << i) # 设置结果该位为1
print(str(ans) + '\n')
复杂度 :预处理 O ( 31 × n ) O(31 \times n) O(31×n),每次查询 O ( 31 ) O(31) O(31)
📝 题目3:异或森林(蓝桥杯3400)⭐难题
题目链接 :蓝桥杯3400
题目描述 :求满足条件的子数组数量,使得子数组所有元素异或结果的因数个数为偶数。
核心转化
- 区间异或 = 前缀异或 :
a[l]^...^a[r] = pre_xor[r] ^ pre_xor[l-1] - 因数个数为偶数 ⟺ 该数为非完全平方数
- 完全平方数的因数个数为奇数(如4的因数:1,2,4)
- 非完全平方数的因数个数为偶数
- 反向思考:总子数组数 - 异或值为完全平方数的子数组数
python
n = int(input())
a = list(map(int, input().split()))
# 前缀异或数组
pre_xor = [0] * n
pre_xor[0] = a[0]
for i in range(1, n):
pre_xor[i] = pre_xor[i - 1] ^ a[i]
ans = 0
# 枚举所有可能的完全平方数(a[i] <= n <= 10^4,异或值范围有限)
for x in range(0, 200):
xor = x * x
# 求异或值等于xor的区间个数
# 即 pre_xor[i] ^ pre_xor[j] = xor 的二元组<i,j>数目,i < j
# 等价于:pre_xor[i] = pre_xor[j] ^ xor
dic = {}
dic[0] = 1 # pre_xor[-1] = 0,处理从0开始的区间
for j in range(n):
ans += dic.get(pre_xor[j] ^ xor, 0)
dic[pre_xor[j]] = dic.get(pre_xor[j], 0) + 1
# 总子数组数 - 异或值为完全平方数的子数组数
total = n * (n + 1) // 2
print(total - ans)
复杂度 :时间 O ( 200 × n ) O(200 \times n) O(200×n),空间 O ( n ) O(n) O(n)
四、位运算常见应用场景总结
| 应用场景 | 位运算技巧 | 时间复杂度 |
|---|---|---|
| 判断奇偶 | x & 1 |
O ( 1 ) O(1) O(1) |
| 交换两数 | a ^= b; b ^= a; a ^= b |
O ( 1 ) O(1) O(1) |
| 判断2的幂 | x & (x-1) == 0 |
O ( 1 ) O(1) O(1) |
| 消去最低位1 | x &= x - 1 |
O ( 1 ) O(1) O(1) |
| 获取最低位1 | x & (-x) |
O ( 1 ) O(1) O(1) |
| 状态压缩DP | 用二进制表示集合 | O ( 2 n × n ) O(2^n \times n) O(2n×n) |
| 树状数组 | lowbit操作 |
O ( log n ) O(\log n) O(logn) |
五、今日打卡练习
📌 练习1:实现一个函数,判断一个数的二进制表示是否为回文。
📌 练习2:给定数组,求所有子数组的异或和之和。
📌 练习3 :LeetCode 136. 只出现一次的数字 ------ 异或经典题
六、结语
位运算的魅力在于用二进制的视角重新审视问题。当你熟练运用这些技巧后,很多看似复杂的题目都会变得简洁优雅。
🌟 学习建议:
- 熟记六大基础运算的真值表
- 重点掌握异或的五大性质(交换律、结合律、自反性等)
- 多练习"按位独立考虑"的思维方式
- 关注
lowbit操作,它是树状数组和线段树的基础
如果本文对你有帮助,欢迎 点赞 👍 + 收藏 ⭐ + 关注 🔔,你的支持是我持续更新的动力!