[CF1515H] Phoenix and Bits

Problem

一个长度为 \(n\) 的数组 \(a\),对其做 4 种操作(下面 \([l,r]\)定义 为:值域\(l\)\(r\) 之间的数)。

  1. \([l,r]\)AND 上一个数 \(x\)
  2. \([l,r]\)OR 上一个数 \(x\)
  3. \([l,r]\)XOR 上一个数 \(x\)
  4. 查询 \([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,所以我们只需实现 ORXOR 即可。(当然也可以利用 ANDXOR 实现 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;
}
posted @ 2021-05-08 16:31  AC-Evil  阅读(356)  评论(0编辑  收藏  举报