通过 Lucas 定理将二维组合数递推转换为按位与条件,再利用高低位分块优化枚举,实现 $O((n+q)2^8)$ 复杂度。
OJ: roj
题目 ID: 20030
难度:省选/NOI-
标签:Lucas 定理高低位分块子集枚举
创建: 2026-10-09 21:43
更新: 2026-10-09 21:43
形式化题目
给定一个长度为
- 对于
,且 ,有
给定
保证查询满足
数据范围:
正解
思路
根据题目的递推式
即,
由 Lucas 定理,(k & x) == k)。
所以:
现在问题变成了求所有满足
高低位分块优化:
我们把
因此有:
我们可以在
对于每次查询,由于
总时间复杂度为预处理
代码
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-09 21:00
# update_at: 2026-10-09 21:16
import sys
from functools import reduce
from operator import xor
BITS = 8 # 低位块宽,低位状态数 2**8 = 256
SIZE = 1 << BITS
HIGH = 1 << (BITS + 1) # x < 2**17 ⇒ x>>8 ≤ 390 < 512,高位子集表留一倍余量
# SUBS[i] = i 的全部子集左移 BITS 位后组成的列表,查询时按 i = x>>BITS 直接查表
SUBS = [[k << BITS for k in range(i + 1) if k & i == k] for i in range(HIGH)]
def build_low_table(a: list[int], n: int) -> list[list[int]]:
"""返回 f:f[j][i] = XOR_{k ⊆ j} a[i + k],即低位部分恰为 j 的询问答案。
取 j 的最低位 low,按"不取 low / 取 low"拆成两支递推:
f[j][i] = f[j ^ low][i] ^ f[j ^ low][i + low]。
第 j 行只会被下标 < n - j 的询问读到,其余位置补 0 占位。
"""
f = [a] + [None] * (SIZE - 1)
for j in range(1, SIZE):
low = j & -j
prev = f[j ^ low]
keep = n - j
row = list(map(xor, prev[:keep], prev[low:keep + low])) if keep > 0 else []
f[j] = row + [0] * ((n - keep) if keep > 0 else n)
return f
def solve() -> None:
data = sys.stdin.buffer.read().split()
if len(data) < 2:
return # 空输入/只有半行:直接退出,不做越界假设
n, q = int(data[0]), int(data[1])
if n <= 0 or len(data) < 2 + n:
return # n=0 或序列长度不足:输入非法,不强行算
a = [int(v) for v in data[2:2 + n]]
f = build_low_table(a, n)
out = []
pos = 2 + n
if len(data) < pos + 2 * q:
return # 查询行数不足:输入非法,不强行算
for _ in range(q):
x, y = int(data[pos]), int(data[pos + 1])
pos += 2
low_row = f[x & (SIZE - 1)] # 低位查预处理表,高位枚举子集异或
out.append(str(reduce(xor, (low_row[y + off] for off in SUBS[x >> BITS]), 0)))
sys.stdout.write('\n'.join(out) + ('\n' if out else ''))
if __name__ == '__main__':
solve()cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-09 21:00
* update_at: 2026-10-09 21:14
*/
// 20030《异或查询》:Lucas 定理 + 高低位分块
// b[x][y] = XOR_{k ⊆ x} a[y + k](组合数 C(x,k) 的奇偶性由 Lucas 定理给出 k & x == k)。
// 低 BLOCK_BITS 位的答案全部预处理,查询时只枚举高位的子集。
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 100005;
const int BLOCK_BITS = 8; // 低位块宽,低位状态数 2^8 = 256
const int BLOCK_SIZE = 1 << BLOCK_BITS;
int a[MAXN]; // 题面输入的初始序列
int f[BLOCK_SIZE][MAXN]; // f[j][i] = XOR_{k ⊆ j} a[i + k],即低 BLOCK_BITS 位为 j 的答案
// 预处理低位表。j 去掉最低位 p 后得到 p_prev,则子集枚举可拆成
// "不取 p" 与 "取 p" 两支,故 f[j][i] = f[p_prev][i] ^ f[p_prev][i + p]。
void build_low_table(int n) {
for (int i = 0; i < n; i++) {
f[0][i] = a[i];
}
for (int j = 1; j < BLOCK_SIZE; j++) {
int p = j & -j; // j 的最低位,作为本次拆分的位权
int p_prev = j ^ p;
for (int i = 0; i + j < n; i++) {
f[j][i] = f[p_prev][i] ^ f[p_prev][i + p];
}
}
}
// 单次询问:x 拆成低位 x2 与高位 x1,枚举 x1 的全部子集 k1,
// 每项查表 f[x2][y + (k1 << BLOCK_BITS)] 后异或起来。
int query(int x, int y) {
int x2 = x & (BLOCK_SIZE - 1);
int x1 = x >> BLOCK_BITS;
int ans = 0;
int k1 = x1;
while (true) {
ans ^= f[x2][y + (k1 << BLOCK_BITS)];
if (k1 == 0) break; // 子集枚举必须先用完 k1 = 0 再退出,否则漏项
k1 = (k1 - 1) & x1; // 枚举 x1 子集的标准位技巧
}
return ans;
}
void solve() {
int n, q;
if (!(cin >> n >> q)) return;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
build_low_table(n);
for (int i = 0; i < q; i++) {
int x, y;
cin >> x >> y;
cout << query(x, y) << "\n";
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
solve();
return 0;
}复杂度
- 时间复杂度:
预处理部分对于每一个
,计算了 个状态,时间复杂度为 。 每次查询枚举高位子集,最多枚举 个子集,时间复杂度为 。 总时间复杂度为 。 - 空间复杂度:
预处理数组
f需要大小为,空间复杂度为 ,约 25MB,符合要求。
总结
- 算法核心:通过 Lucas 定理将组合数模 2 的问题转化为子集枚举,再通过高低位分块思想,平衡预处理与查询的时间,将查询复杂度从
优化为 。 - 证据层升级与对拍结论:本题不再是无数据题。官方提供的大数据样例
xor.in/xor.ans(规模)在验证中与 main.cpp和main.py的输出逐字节一致。worker 跨源对拍结论表明代码在大规模数据下表现优异且正确。 - 数据规模与块大小:
,这恰好适合块大小取 (即 256)。预处理复杂度为 ,查询复杂度不超过 ,常数很小,足以在限定时间内通过。 - 溢出处理(T9 五项):本题递推只涉及异或操作,没有加减乘除。由于异或操作本质上是不进位加法,其中间结果始终不会超过操作数原有的最大位宽(
为普通整数范围内),所以使用 32 位整型 int即可,不会发生溢出。