简介
其实就是只有两个分治的 $Trie$
应用
在 $Trie$ 里找与 $v$ 的异或的最大值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const int N = 30;
struct Trie {
int v, ch[2];
} T[maxn * 31]; int rt = 1, top = 1;
void insert(int k) {
int i = rt;
for (int o = N; ~o; --o) {
int d = k >> o & 1;
if (!T[i].ch[d]) T[i].ch[d] = ++top;
i = T[i].ch[d];
}
}
int query(int i, int k, int o) {
if (o == -1) return 0;
int d = k >> o & 1;
if (T[i].ch[d ^ 1]) return (1 << o) + query(T[i].ch[d ^ 1], k, o - 1);
else return query(T[i].ch[d], k, o - 1);
}在 $Trie$ 里找与 $x$ 异或小于 $y$ 的值的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const int N = 30;
struct Trie {
int v, ch[2];
} T[maxn * 30]; int rt = 1, top = 1;
inline void maintain(int i) { T[i].v = T[lc].v + T[rc].v; }
void ins(int &i, int k, int o, int v) {
if (!i) i = ++top;
if (o == -1) return (void) (T[i].v += v);
int d = k >> o & 1; ins(T[i].ch[d], k, o - 1, v);
maintain(i);
}
int query(int i, int k1, int k2, int o) {
if (!i) return 0;
if (o == -1) return T[i].v;
int d = k1 >> o & 1, D = k2 >> o & 1;
if (D) return T[T[i].ch[d]].v + query(T[i].ch[d ^ 1], k1, k2, o - 1);
else return query(T[i].ch[d], k1, k2, o - 1);
}在 $Trie$ 里找异或第 $k$ 小或者前 $k$ 小的和
找第 $k$ 小就用类似权值线段树的方法,下面是一份滚动的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20for (int o = 61; ~o; --o) {
top = o & 1 ? 1 : n + 1;
for (int i = 1; i <= n; ++i) {
int &p = a[i], d = w[i] >> o & 1;
if (!T[p].ch[d]) {
T[++top].clear();
T[p].ch[d] = top;
}
++T[p = T[p].ch[d]].v;
} ll s = 0;
for (int i = 1; i <= n; ++i) {
int p = b[i], d = w[i] >> o & 1;
s += T[T[p].ch[d]].v;
} int v = 0;
if (k > s) k -= s, ans |= 1ll << o, v = 1;
for (int i = 1; i <= n; ++i) {
int &p = b[i], d = w[i] >> o & 1;
p = T[p].ch[d ^ v];
}
}在 $Trie$ 里找 $and$ 或者 $or$ 最大值
好像是将 $0$ 的树向 $1$ 的树合并,还没做过
全局加 $1$,全局异或和
Luogu P3760 [TJOI2017]异或和(01Trie)
首先我们将 $Trie$ 按照二进制位从低到高建树
这样我们在加 $1$ 时所进行的操作就是不断进入右子树,然后交换左右子树,然后还要维护异或值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const int N = 20;
struct Trie {
int v, sum, ch[2];
} T[Maxn * 21]; int rt = 1, top = 1;
inline void maintain(int i) {
T[i].v = T[lc].v + T[rc].v;
T[i].sum = T[lc].sum << 1;
T[i].sum ^= T[rc].sum << 1 | T[rc].v & 1;
}
void ins(int &i, int k, int o, int v) {
if (!i) i = ++top;
if (o == N + 1) return (void) (T[i].v += v);
int d = k >> o & 1; ins(T[i].ch[d], k, o + 1);
maintain(i);
}
void add(int i) {
if (!i) return ;
add(rc); swap(lc, rc);
maintain(i);
}
例题
简要题意:给定一个长度为 $n$ 的序列 $a_i$,求所有区间和的异或和
$n\le 10^5,\sum a_i\le 10^6$
简要题解:我们要求这个东西 $\bigoplus_{1\le j\le i\le n}s_i-s_{j-1}$
考虑这样一个做法,我们枚举右端点,维护左端点的异或值
那么每次相当于全局加 $1$,求全局异或和,这个东西可以拿 $01Trie$ 来做
时间复杂度为 $O((n+\sum a_i)\log \sum a_i)$
简要题解:给定一棵 $n$ 个点有点权的无根树,求第 $k$ 小的路径异或和
$n\le 10^6$
简要题解:找两两异或值的前 $k$ 小类似于在权值线段树上二分
由于爆空间,所以这题只能滚动
简要题意:给定一个长度为 $n$ 的序列 $a_i$,现在要将每个数划分到两个集合,一个几何的权值为集合内的所有数,两两之间的异或和的最小值,现在要求一种分法,使得两个点集的权值的最小值最大
$n\le 10^5$
简要题解:首先考虑一个比较套路的做法,根据 Luogu P1525 [NOIP2010 提高组] 关押罪犯(最小生成树),容易得到我们只需要求一个最小生成树,然后用 $01Trie$ 求一个最小值即可,求异或最小生成树是一个经典的做法,时间复杂度 $O(n\log^2 n)$
另外还有一个做法,现在我们仿照求异或最小生成树的做法,按位分治
我们考虑现在在考虑第 $k$ 位,令第 $k$ 位为 $1$ 的个数为 $a$,第 $k$ 位为 $0$ 的个数为 $b$
注意到如果 $a$ 和 $b$ 都小于等于 $2$,那么我们可以通过分组来避免最小值小于 $2^k$
否则,最小值一定不含第 $k$ 位,且一定由左边和右边组成,所以可以直接递归
时间复杂度 $O(n\log n)$
简要题意:给定一个长度为 $n$ 的数列和一个数字 $k$,求从这 $n$ 歌数中找出一个大小最大的子集,满足这个集合中任意两个数的异或和都大于等于 $k$
$n\le 3\times 10^5$
简要题解:首先得到 $k$ 的最高二进制一的位置,令其为 $t$,那么我们容易发现如果如果两个数字 $t$ 之前的位置有所不同,那么这两个数字的异或和一定大于 $k$,注意到这是一个前缀的比较,那么我们可以联想到 $01Trie$
我们将这些点拉出来,发现他们内部选出来的点与外面的点是没有影响的,然后容易发现,这些点内部最多选两个点,左儿子一个,右儿子一个,因为左右儿子内部选出来的点异或最大不会超过 $2^{t}-1$,那么这里我们只需要枚举左儿子的点,然后对于每个点从右儿子中选一个最大的,在 $01Trie$ 上实现这点是非常容易的,最后选择异或和最大的一对点即可
时间复杂度 $O(n\log n)$
CF 1625D Binary Spiders