一、位运算基础速查表
在刷题之前,先快速回顾六大基础运算:
| 运算 | 符号 | 规则 | 实例 |
|---|---|---|---|
| 与 | & |
有0则0,全1为1 | 6 & 11 = 2 |
| 或 | ` | ` | 有1则1,全0为0 |
| 异或 | ^ |
相同为0,不同为1 | 6 ^ 11 = 13 |
| 取反 | ~ |
1变0,0变1 | ~6 = -7 |
| 左移 | << |
左移n位 = 乘 2n2^n2n | 5 << 3 = 40 |
| 右移 | >> |
右移n位 = 除 2n2^n2n | 13 >> 2 = 3 |
异或五大性质(必背!)
- 交换律 :
x ^ y = y ^ x - 结合律 :
x ^ (y ^ z) = (x ^ y) ^ z - 自反性 :
x ^ x = 0 - 零元素 :
x ^ 0 = x - 逆运算 :若
x ^ y = z,则z ^ y = x
六大技巧速查
| 技巧 | 代码 | 时间 |
|---|---|---|
| 判断奇偶 | x & 1 |
O(1)O(1)O(1) |
| 获取第i位 | (x >> i) & 1 |
O(1)O(1)O(1) |
| 设置第i位为1 | `x | = (1 << i)` |
| 设置第i位为0 | x &= ~(1 << i) |
O(1)O(1)O(1) |
| 判断2的幂 | x & (x-1) == 0 |
O(1)O(1)O(1) |
| 获取最低位1 | x & (-x) |
O(1)O(1)O(1) |
二、例题精讲
例题 1:二进制中1的个数
| 项目 | 内容 |
|---|---|
| 链接 | https://www.lanqiao.cn/problems/1331/learning/ |
| 类型 | 位运算基础 |
| 核心 | 逐位判断 / lowbit优化 |
题目描述
给定整数 xxx,输出其二进制表示中1的个数。
输入 :32位整数 xxx(注意:包含负数!)
输出: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
⚠️ 注意 :本题输入含负数,负数在Python中右移会无限填充1,因此必须使用32位遍历法!
例题 2:区间或
| 项目 | 内容 |
|---|---|
| 链接 | https://www.lanqiao.cn/problems/3691/learning/ |
| 类型 | 位运算 + 前缀和 |
| 核心 | 按位独立考虑,前缀和优化查询 |
题目描述
给定数组 aaa,qqq 次询问,每次求区间 [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)
关键细节
| 坑点 | 说明 |
|---|---|
| 按位独立 | 或运算满足按位独立,可以分别处理每一位 |
| 前缀和优化 | 用前缀和 O(1)O(1)O(1) 判断区间内某位是否有1 |
l=0特判 |
前缀和数组从0开始,需要单独处理左边界 |
例题 3:异或森林 ⭐难题
| 项目 | 内容 |
|---|---|
| 链接 | https://www.lanqiao.cn/problems/3400/learning/ |
| 类型 | 位运算 + 前缀异或 + 哈希 |
| 核心 | 反向思考:总区间数 - 不满足条件的区间数 |
题目描述
求满足条件的子数组数量,使得子数组所有元素异或结果的因数个数为偶数。
核心转化
- 区间异或 = 前缀异或 :
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)
关键细节
| 坑点 | 说明 |
|---|---|
| 数学转化 | 因数个数为偶数 ⟺ 非完全平方数,这是解题关键 |
| 反向思考 | 直接求"因数个数为偶数"很难,改为求总数减去"完全平方数" |
| 哈希优化 | 用字典统计前缀异或出现次数,O(1)O(1)O(1) 查询配对 |
| 枚举范围 | 异或值上限约为 2×1042 \times 10^42×104,完全平方数只需枚举到200 |
例题 4:相或为k
| 项目 | 内容 |
|---|---|
| 类型 | 位运算 + 贪心筛选 |
| 核心 | 利用或运算性质:如果 `a[i] |
题目描述
给定 nnn 个非负整数 a[i]a[i]a[i],是否可以从中选出一些数进行或运算使得值为 kkk?
关键思路
- 筛选条件 :如果
a[i] \| k == k,说明a[i]的二进制1全部包含在k的1中- 反过来说,
a[i]没有k以外的1 - 这样的数才可能参与组成
k
- 反过来说,
- 验证 :将所有满足条件的数做或运算,看结果是否等于
k
推演验证
输入: n=4, k=6, a=[1,2,3,4]
k = 6 = 110
a[0]=1=001: 1|6 = 111 != 6,不满足(1的第0位1不在k中)
a[1]=2=010: 2|6 = 110 == 6,满足 ✓
a[2]=3=011: 3|6 = 111 != 6,不满足(3的第0位1不在k中)
a[3]=4=100: 4|6 = 110 == 6,满足 ✓
满足条件的数:2, 4
或运算:2 | 4 = 6 == k ✓
输出: Yes
题解
python
t = int(input())
for _ in range(t):
n, k = map(int, input().split())
a = list(map(int, input().split()))
huo = 0
for i in a:
# 筛选:a[i]的1必须全部在k的1中
if i | k == k:
huo |= i # 累加或运算
if huo == k:
print("Yes")
else:
print("No")
复杂度 :时间 O(t×n)O(t \times n)O(t×n),空间 O(1)O(1)O(1)
关键细节
| 坑点 | 说明 |
|---|---|
| 筛选逻辑 | `i |
| 为什么筛选后或运算即可 | 如果筛选后的数或起来不等于 k,说明 k 的某些位无法被覆盖 |
| 贪心正确性 | 或运算只会增加1不会减少,所以全部选上是最优的 |
例题 5:异或王国 ⭐⭐⭐⭐
| 项目 | 内容 |
|---|---|
| 类型 | 位运算 + 按位统计 + 组合数学 |
| 核心 | 按位独立计算贡献,统计每位的0和1数量 |
题目描述
给定长度为 nnn 的序列,任选两个数,求所有方案的异或值之和,对 109+710^9+7109+7 取模。
∑i=1n−1∑j=i+1n(ai⊕aj)mod (109+7)\sum_{i=1}^{n-1} \sum_{j=i+1}^{n} (a_i \oplus a_j) \mod (10^9+7)i=1∑n−1j=i+1∑n(ai⊕aj)mod(109+7)
关键思路
按位独立考虑:异或运算的每一位互不影响,可以分别计算每一位的贡献再相加。
对于第 kkk 位:
- 该位为1的条件:两个数该位不同(一个0一个1)
- 假设有 bbb 个1,(n−b)(n-b)(n−b) 个0
- 异或结果为1的方案数:b×(n−b)b \times (n-b)b×(n−b)
- 第 kkk 位的贡献:b×(n−b)×2kb \times (n-b) \times 2^kb×(n−b)×2k
推演验证
输入: n=3, a=[1,2,3]
1 = 01, 2 = 10, 3 = 11
第0位:1(1), 2(0), 3(1) → b=2个1, n-b=1个0
贡献 = 2 * 1 * 2^0 = 2
第1位:1(0), 2(1), 3(1) → b=2个1, n-b=1个0
贡献 = 2 * 1 * 2^1 = 4
总和 = 2 + 4 = 6 ✓
验证:(1^2) + (1^3) + (2^3) = 3 + 2 + 1 = 6 ✓
题解
python
mod = 1000000007
n = int(input())
li = list(map(int, input().split()))
# 创建字典,键是二进制的位数(0~59),值是该位所有元素的值(0或1)
diro = {i: [] for i in range(60)}
for i in range(60):
for j in range(n):
diro[i].append((li[j] >> i) & 1)
ans = 0
for k in range(60):
a = sum(diro[k]) # 第k位1的个数
# 0的个数 = n - a
# 异或为1的方案数 = a * (n - a)
ans = (ans + (a * (n - a)) * pow(2, k, mod)) % mod
print(ans)
复杂度 :时间 O(60×n)O(60 \times n)O(60×n),空间 O(60×n)O(60 \times n)O(60×n)(可优化到 O(1)O(1)O(1) 空间)
空间优化版
python
mod = 1000000007
n = int(input())
li = list(map(int, input().split()))
ans = 0
for k in range(60):
cnt1 = 0
for j in range(n):
cnt1 += (li[j] >> k) & 1
ans = (ans + cnt1 * (n - cnt1) * pow(2, k, mod)) % mod
print(ans)
关键细节
| 坑点 | 说明 |
|---|---|
| 按位独立 | 异或的每一位互不影响,这是位运算题目的核心思想 |
| 组合数学 | 两个数异或为1 ⟺ 一个0一个1,方案数 = cnt0 * cnt1 |
| 取模 | 答案可能非常大,每一步都要取模 |
| 数据范围 | ai<260a_i < 2^{60}ai<260,所以只需遍历0~59位 |
例题 6:异或和之和 ⭐⭐⭐⭐⭐ 压轴难题
| 项目 | 内容 |
|---|---|
| 类型 | 位运算 + 前缀异或 + 按位贡献 |
| 核心 | 求所有子段的异或和之和,按位统计贡献 |
题目描述
给定数组 AiA_iAi,分别求其每个子段的异或和,并求出它们的和。
即求:∑1≤L≤R≤n(AL⊕AL+1⊕...⊕AR)\sum_{1 \leq L \leq R \leq n} (A_L \oplus A_{L+1} \oplus ... \oplus A_R)∑1≤L≤R≤n(AL⊕AL+1⊕...⊕AR)
关键思路
第一步:转化为前缀异或
设 pre[i]=A1⊕A2⊕...⊕Aipre[i] = A_1 \oplus A_2 \oplus ... \oplus A_ipre[i]=A1⊕A2⊕...⊕Ai,则区间 [L,R][L,R][L,R] 的异或和为:
AL⊕...⊕AR=pre[R]⊕pre[L−1]A_L \oplus ... \oplus A_R = pre[R] \oplus pre[L-1]AL⊕...⊕AR=pre[R]⊕pre[L−1]
第二步:按位独立统计
对于第 kkk 位,我们需要统计有多少对 (i,j)(i,j)(i,j) 满足 pre[i]⊕pre[j]pre[i] \oplus pre[j]pre[i]⊕pre[j] 的第 kkk 位为1。
第三步:统计前缀异或中0和1的数量
遍历数组,维护第 kkk 位前缀异或的当前值 sum(0或1):
- 如果
sum == 0:当前前缀异或第 kkk 位为0,与之前所有第 kkk 位为1的前缀异或配对,异或结果为1 - 如果
sum == 1:当前前缀异或第 kkk 位为1,与之前所有第 kkk 位为0的前缀异或配对,异或结果为1
推演验证
输入: n=5, a=[1,2,3,4,5]
前缀异或:
pre[0] = 0
pre[1] = 1 = 001
pre[2] = 1^2 = 3 = 011
pre[3] = 3^3 = 0 = 000
pre[4] = 0^4 = 4 = 100
pre[5] = 4^5 = 1 = 001
第0位:pre = [0,1,1,0,0,1]
zero=1(初始pre[0]=0), one=0
i=1, v=1: sum=1, one=1, cnt+=zero=1, cnt=1
i=2, v=1: sum=0, zero=2, cnt+=one=1, cnt=2
i=3, v=0: sum=0, zero=3, cnt+=one=1, cnt=3
i=4, v=0: sum=0, zero=4, cnt+=one=1, cnt=4
i=5, v=1: sum=1, one=2, cnt+=zero=4, cnt=8
贡献 = 8 * 1 = 8
第1位:pre = [0,0,1,1,0,0]
...(类似计算)
最终答案 = 32 ✓
题解
python
n = int(input())
a = [int(x) for x in input().split()]
ans = 0
for k in range(21): # 所有a不超过20位
zero, one = 1, 0 # 统计第k位的0和1的数量,初始pre[0]=0
cnt, sum_val = 0, 0 # cnt: 第k位异或为1的配对数
for i in range(n):
v = (a[i] >> k) & 1 # 取a[i]的第k位
sum_val ^= v # 前缀异或的第k位
if sum_val == 0: # 前缀和为0
zero += 1 # 0的数量加1
cnt += one # 与前面等于1的前缀异或配对,结果为1
else: # 前缀异或为1
one += 1 # 1的数量加1
cnt += zero # 与前面等于0的前缀异或配对,结果为1
ans += cnt * (1 << k) # 第k位的贡献
print(ans)
复杂度 :时间 O(21×n)O(21 \times n)O(21×n),空间 O(1)O(1)O(1)
关键细节
| 坑点 | 说明 |
|---|---|
| 前缀异或数组 | pre[0] = 0 必须初始化,处理从第一个元素开始的区间 |
| zero初始化为1 | 因为 pre[0] = 0,所以0的初始数量为1 |
| sum的含义 | 是当前前缀异或的第k位值,不是累加和 |
| cnt的统计 | 每次遇到0,与前面所有1配对;遇到1,与前面所有0配对 |
| 为什么这样统计 | 两个前缀异或第k位不同 ⟺ 区间异或和的第k位为1 |
三、今日刷题总结
| 题号 | 题目 | 考点 | 难度 | 核心技巧 |
|---|---|---|---|---|
| 1 | 二进制中1的个数 | 逐位判断 | ⭐⭐ | (x>>i)&1 |
| 2 | 区间或 | 前缀和+按位 | ⭐⭐⭐ | 按位独立,前缀和优化 |
| 3 | 异或森林 | 前缀异或+哈希 | ⭐⭐⭐⭐⭐ | 反向思考,数学转化 |
| 4 | 相或为k | 或运算性质 | ⭐⭐⭐ | `i |
| 5 | 异或王国 | 按位统计 | ⭐⭐⭐⭐ | cnt0 * cnt1 * 2^k |
| 6 | 异或和之和 | 前缀异或+按位 | ⭐⭐⭐⭐⭐ | 配对统计,zero/one计数 |
位运算解题模板
python
# 模板1:按位统计贡献
for k in range(60): # 遍历每一位
cnt1 = 0
for x in a:
cnt1 += (x >> k) & 1
cnt0 = n - cnt1
ans += cnt1 * cnt0 * (1 << k)
# 模板2:前缀异或 + 哈希
pre_xor = [0] * (n + 1)
for i in range(n):
pre_xor[i+1] = pre_xor[i] ^ a[i]
dic = {0: 1} # 初始化
for i in range(1, n+1):
# 查询配对
ans += dic.get(pre_xor[i] ^ target, 0)
dic[pre_xor[i]] = dic.get(pre_xor[i], 0) + 1
# 模板3:筛选后或运算
target = k
result = 0
for x in a:
if x | target == target: # x的1全部在target中
result |= x
# result == target ?
四、结语
位运算的核心思想就一句话:按位独立考虑,利用二进制性质简化问题。
🌟 今日收获:
- 掌握了六大基础运算和六大技巧
- 学会了"按位独立"的思维方式
- 掌握了前缀异或 + 哈希的经典套路
- 理解了反向思考、数学转化等高级技巧
位运算题目看似复杂,但只要抓住"按位独立"这个核心,很多难题都会迎刃而解。继续加油,国赛见!💪
如果本文对你有帮助,欢迎 点赞 👍 + 收藏 ⭐ + 关注 🔔,你的支持是我持续更新的动力!