2021牛客多校第三场 I. Kuriyama Mirai and Exclusive Or (位运算+差分)
题意就是给你一个长度为n的数组,之后有两种操作,第一种是[L, R]都异或上x,第二种是[L, R]异或上x, x+1, x+2, x+3...
对于第一个操作我们非常容易就能操作,即异或差分数组。
但是对于第二个操作我们就比较头疼了,我们要怎么把 + 符号去掉变成 ^ 呢。 (因为异或是不满足分配律的所以我们需要想办法把这个+号去掉。
考虑到如果我们将 x 分为 k块(k为x的二进制1的个数)。
为什么这样分呢,因为我们发现假设 lowbit(x) = k, 那么a1 ^ (x + 1) 就会等价于 a1 ^ x ^ 1因为在lowbit前面的都是0,0异或任何数字就是加上这个数字。
并且我们设置一个标记数组flag[i][j],表示从i位置开始,需要往长度为2的 j 次的块上,异或上0, 1, 2..., 2的 j 次-1
所以操作2,其实就是往块的两边^ 2的k次,之后再给块的开头打上标记。
之后考虑如何将标记下传到原数组。这里其实像是一个倍增的思想,也很像线段树里的push_down,就是说如果flag[ i ] [ j ]有标记的话,就意味着,i - i+2的(j-1)次, i+2的(j-1)次 - i + 2的 j 次上都会需要有这个操作。
于是我们就可以将flag[ i ] [ j ]下放到, flag[ i ] [ j-1]和 flag[ i + 2的j-1次] [ j-1 ],同时对差分数组的 i + 2的j-1次与i + 2的j次的位置异或上 2的j-1次。就相当于是,往后半个块上异或上 2 的 j -1次。
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int, int> pii; const int inf = 0x3f3f3f3f; ///1061109567 const int maxn = 6e5 + 10; int chafen[maxn]; int flag[maxn][21]; int a[maxn], pw[21]; int main() { int n, q; scanf("%d%d", &n, &q); for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]); pw[0] = 1; for (int i = 1; i <= 20; ++ i) pw[i] = pw[i-1]*2; while (q--) { int op; scanf("%d", &op); int l, r, x; scanf("%d%d%d", &l, &r, &x); if (op == 0) { chafen[l] ^= x; chafen[r+1] ^= x; } else { for (int j = 0; j <= 20; ++ j) { if (((x >> j) & 1) && (l + pw[j] - 1) <= r) { chafen[l] ^= x; chafen[l+pw[j]] ^= x; flag[l][j] ^= 1; x += pw[j]; l += pw[j]; } } for (int j = 20; j >= 0; j --) { if (l + pw[j]-1 <= r) { chafen[l] ^= x; chafen[l+pw[j]] ^= x; flag[l][j] ^= 1; x += pw[j]; l += pw[j]; } } } } for (int j = 20; j >= 1; j --) { for (int i = 1; i <= n; ++ i) { if (flag[i][j]) { flag[i][j-1] ^= 1; flag[i+pw[j-1]][j-1] ^= 1; chafen[i+pw[j-1]] ^= pw[j-1]; chafen[i+pw[j]] ^= pw[j-1]; } } } for (int i = 1; i <= n; ++ i) { chafen[i] ^= chafen[i-1]; printf("%d%c", a[i] ^ chafen[i], " \n"[i==n]); } return 0; }