本文主要记录算法竞赛中简单的位运算知识.
异或
性质
自反律:,;
交换律:;
结合律:;
⭐ 三个对象 ,, 地位等价:
⭐ 加法 异或:;
加法会进位,而异或不会,因此上式成立.
何时取等?只要不进位就取等.
由于三者地位等价,因此 ,,故 ,于是得到异或的大致范围:相邻异或和:
当 为偶数时,;
异或和
对于序列 ,,,,求
无非就是
因此根据乘法分配律,(可以看成上面的式子混入了最终的结果, 号被解离)
核心就是求 ,但 从哪来?从 和 来:
这里 ,, 要么是 ,要么是 .因此从序列 , 单独拎出比特位第 位即可得到新序列 , ,这些新序列其实都是 序列.
说是求两组数的异或和,其实要干这样一件事:只单独看某一比特位,两组数都变成了 序列,我们要求的是从这两个 序列中分别取出一个比特,能组成 或者 对的方案数,最后将这个方案数安置在最终结果比特位第 位上.
例如序列 ,要求
关注比特位第 位到第 位,分别有
从 拎出两个比特,能组成 无序对的方案有 种;
从 拎出两个比特,能组成 无序对的方案有 种;
从 拎出两个比特,能组成 无序对的方案有 种;
从 拎出两个比特,能组成 无序对的方案有 种;
把这 个数安置在最终答案的比特位里:
参考代码:
cpp
#define int long long;
constexpr int MAXB = 32;
constexpr int MOD = 1e9 + 7;
int xor_sum(vector<int> &a, vector<int> &b, int n, int m) {
// 前缀优化
vector<vector<int>> a0pre(MAXB, vector<int>(n + 1));
vector<vector<int>> a1pre(MAXB, vector<int>(m + 1));
vector<vector<int>> b0pre(MAXB, vector<int>(n + 1));
vector<vector<int>> b1pre(MAXB, vector<int>(m + 1));
auto _get = [](vector<int> &arr, int L, int R) {
return arr[R] - arr[L - 1];
};
for (int k = 0; k < MAXB; k++) {
for (int i = 1; i <= n; i++) {
if ((a[i - 1] >> k) & 1) {
a1pre[k][i] = a1pre[k][i - 1] + 1; // 统计第 k 位是 1 的个数的前缀和
a0pre[k][i] = a0pre[k][i - 1];
} else {
a0pre[k][i] = a0pre[k][i - 1] + 1; // 统计第 k 位是 0 的个数的前缀和
a1pre[k][i] = a1pre[k][i - 1];
}
}
for (int j = 1; j <= m; j++) {
if ((b[j - 1] >> k) & 1) {
b1pre[k][i] = b1pre[k][i - 1] + 1;
b0pre[k][i] = b0pre[k][i - 1];
} else {
b0pre[k][i] = b0pre[k][i - 1] + 1;
b1pre[k][i] = b1pre[k][i - 1];
}
}
}
// 多次查询
int q;
cin >> q;
while (q--) {
int aL, aR, bL, bR;
cin >> aL >> aR >> bL >> bR;
int ans = 0;
for (int k = 0; k < MAXB; k++) {
int tmp;
tmp = _get(a0pre[k], aL, aR) * _get(b1pre[k], bL, bR); // a, b 的第 k 位分别是 0, 1
tmp %= MOD;
tmp <<= k; // 容易漏
tmp %= MOD;
ans += tmp;
ans %= MOD;
tmp = _get(a1pre[k], aL, aR) * _get(b0pre[k], bL, bR); // a, b 的第 k 位分别是 1, 0
tmp %= MOD;
tmp <<= k; // 容易漏
tmp %= MOD;
ans += tmp;
ans %= MOD;
}
cout << ans << endl;
}
}判定
- 判断是否为 的幂:
x & (x - 1) - 截取第 位是否为 :
(x >> k) & 1 - 树状数组中的
lowbit(x):x & -x