备战蓝桥杯国赛【Day 9】

一、位运算基础速查表

在刷题之前,先快速回顾六大基础运算:

运算 符号 规则 实例
& 有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/
类型 位运算 + 前缀异或 + 哈希
核心 反向思考:总区间数 - 不满足条件的区间数
题目描述

求满足条件的子数组数量,使得子数组所有元素异或结果的因数个数为偶数

核心转化
  1. 区间异或 = 前缀异或a[l]^...^a[r] = pre_xor[r] ^ pre_xor[l-1]
  2. 因数个数为偶数 ⟺ 该数为非完全平方数
    • 完全平方数的因数个数为奇数(如4的因数:1,2,4)
    • 非完全平方数的因数个数为偶数
  3. 反向思考:总子数组数 - 异或值为完全平方数的子数组数
题解
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 ?

四、结语

位运算的核心思想就一句话:按位独立考虑,利用二进制性质简化问题

🌟 今日收获

  1. 掌握了六大基础运算和六大技巧
  2. 学会了"按位独立"的思维方式
  3. 掌握了前缀异或 + 哈希的经典套路
  4. 理解了反向思考、数学转化等高级技巧

位运算题目看似复杂,但只要抓住"按位独立"这个核心,很多难题都会迎刃而解。继续加油,国赛见!💪


如果本文对你有帮助,欢迎 点赞 👍 + 收藏 ⭐ + 关注 🔔,你的支持是我持续更新的动力!

相关推荐
洛水水1 小时前
【力扣100题】20.合并 K 个升序链表
算法·leetcode·链表
m0_748554811 小时前
uni-app怎么实现App指纹登录 uni-app生物识别API接入流程【详解】
jvm·数据库·python
2301_809204701 小时前
c++字符串运算_连接、比较、输入输出等运算符重载应用
jvm·数据库·python
闻缺陷则喜何志丹1 小时前
【动态规划 前缀和】P7074 [CSP-J2020] 方格取数|普及+
c++·算法·前缀和·动态规划·洛谷
a7963lin2 小时前
PHP怎么实现单例模式_PHP常用设计模式之单例模式【方法】
jvm·数据库·python
Liangwei Lin2 小时前
LeetCode 74. 搜索二维矩阵
算法·leetcode·矩阵
phltxy2 小时前
Redis Hash 数据类型:详解命令与实战场景
redis·算法·哈希算法
Aision_5 小时前
从工具调用到 MCP、Skill完整学习记录
java·python·gpt·学习·langchain·prompt·agi
放羊郎9 小时前
基于ORB-SLAM2算法的优化工作
人工智能·算法·计算机视觉