[CF1515H] Phoenix and Bits
Problem
一个长度为 \(n\) 的数组 \(a\),对其做 4 种操作(下面 \([l,r]\) 的 定义 为:值域 在 \(l\) 到 \(r\) 之间的数)。
- \([l,r]\) 内
AND
上一个数 \(x\); - \([l,r]\) 内
OR
上一个数 \(x\); - \([l,r]\) 内
XOR
上一个数 \(x\); - 查询 \([l,r]\) 内不同的数的个数
\(n\le 2\times 10^5\),\(Q\le 10^5\),值域 \(\in[0,2^{20})\)。
Sol
(以下设全集为 M
\(=\) 11..11
\(=2^{20}-1\))。
所有操作均在 Trie
上进行。在 \([l,r]\) 上操作需要使用 线段树分裂
取出该段区间,最后合并回去即可。
由于 x AND y
\(=\) ((x XOR M) OR (y XOR M)) xor M
,所以我们只需实现 OR
和 XOR
即可。(当然也可以利用 AND
和 XOR
实现 OR
)
对于 XOR
操作,很简单,只需要对子树打上标记即可;
对于 OR
操作比较困难。仔细分析,OR
操作经常会合并一些节点,这种情况当且仅当操作数 \(x\) 当前位置为 \(1\) 以及在该层中即含有 0 又含有 1,此时我们暴力合并两棵子树,复杂度为 \(\mathcal O(size)\)。合并 次数只与节点个数有关,\(\le n\log V\)。
但如果不满足情况呢?如果子树内有可以合并的节点,就递归合并,否则子树内只剩下一批左节点 swap 到右节点的情况,此时利用 XOR
打标记即可。
这样做我们需要维护:区间 XOR
标记、区间中左儿子和右儿子在每一层是否存在、区间答案数。由于 OR
操作经常 \(\mathcal O(\log V)\) 换一次节点合并,而合并次数不超过 \(\mathcal O(n\log V)\),总复杂度为 \(\mathcal O(n\log^2V)\)。
Code
#include <bits/stdc++.h>
const int N = 200005, M = 1 << 20;
int n, Q, rt = 0;
int lc[N * 50], rc[N * 50], tagx[N * 50], tagl[N * 50], tagr[N * 50], sum[N * 50], tot = 0;
void up(int o) {
tagl[o] = tagl[lc[o]] | tagl[rc[o]];
tagr[o] = tagr[lc[o]] | tagr[rc[o]];
sum[o] = sum[lc[o]] + sum[rc[o]];
}
void pushx(int o, int x, int dep) {
if (x >> dep & 1) std::swap(lc[o], rc[o]);
int L = tagl[o], R = tagr[o];
tagl[o] = (L & (x ^ (M - 1))) | (R & x);
tagr[o] = (L & x) | (R & (x ^ (M - 1)));
tagx[o] ^= x;
}
void down(int o, int dep) {
if (!tagx[o]) return;
pushx(lc[o], tagx[o], dep - 1), pushx(rc[o], tagx[o], dep - 1);
tagx[o] = 0;
}
void ins(int &o, int x, int dep) {
if (!o) o = ++tot;
if (dep == -1) {
tagl[o] = x ^ (M - 1), tagr[o] = x, sum[o] = 1;
return;
}
x >> dep & 1 ? ins(rc[o], x, dep - 1) : ins(lc[o], x, dep - 1);
up(o);
}
void split(int &x, int &y, int l, int r, int L, int R, int dep) {
if (!x) return;
if (L <= l && r <= R) {
y = x, x = 0;
return;
}
int mid = l + r >> 1; down(x, dep);
y = ++tot;
if (L <= mid) split(lc[x], lc[y], l, mid, L, R, dep - 1);
if (mid < R) split(rc[x], rc[y], mid + 1, r, L, R, dep - 1);
up(x), up(y);
}
void merge(int &x, int y, int dep) {
if (!x || !y) { x += y; return; }
if (dep == -1) return;
down(x, dep), down(y, dep);
merge(lc[x], lc[y], dep - 1), merge(rc[x], rc[y], dep - 1);
up(x);
}
void pushor(int &o, int x, int dep) {
if (dep == -1 || !o) return;
if (!(x & tagl[o] & tagr[o])) {
pushx(o, x & tagl[o], dep);
return;
}
down(o, dep);
if (x >> dep & 1) {
pushx(lc[o], 1 << dep, dep - 1); // 这里很重要:虽然不会对子树交换,但会修改 tagl 和 tagr。
merge(rc[o], lc[o], dep - 1);
lc[o] = 0;
}
pushor(lc[o], x, dep - 1), pushor(rc[o], x, dep - 1);
up(o);
}
int main() {
scanf("%d%d", &n, &Q);
for (int i = 1; i <= n; i++) {
int x; scanf("%d", &x);
ins(rt, x, 19);
}
while (Q--) {
int ty, l, r, now = 0; scanf("%d%d%d", &ty, &l, &r);
split(rt, now, 0, M - 1, l, r, 19);
if (ty == 4) printf("%d\n", sum[now]);
else {
int x; scanf("%d", &x);
if (ty == 1) pushx(now, M - 1, 19), pushor(now, x ^ (M - 1), 19), pushx(now, M - 1, 19);
if (ty == 2) pushor(now, x, 19);
if (ty == 3) pushx(now, x, 19);
}
merge(rt, now, 19);
}
return 0;
}