这里用递归实现01-trie, 可以看做是区间长度为2的幂的权值线段树, 能实现权值的所有操作, 异或时, 翻转左右儿子即可.
练习1 CF 817E Choosing The Commander
大意: 要求维护一个集合, 给定三种操作:
1. 插入属性$p_i$的元素
2. 删除属性$p_i$的元素
3. 询问集合中有多少元素异或$p_i$后的值小于$l_i$
算是比较简单的01-trie入门题, 假若每次都异或0的话, 完全就是裸的权值, 非0的话, 对于非0位翻转左右儿子即可
#include <iostream>
#include <algorithm>
#include <math.h>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
const int N = 1e5+10, sz = 27;
int n, T, tot;
struct {int ch[2],sum;} tr[N<<5];
void add(int &o, int d, int x, int v) {
if (!o) o=++tot;
tr[o].sum += v;
if (d>=0) add(tr[o].ch[x>>d&1],d-1,x,v);
}
int query(int o, int d, int x, int v) {
if (!o) return 0;
int f1 = x>>d&1, f2 = v>>d&1;
if (f2) return tr[tr[o].ch[f1]].sum+query(tr[o].ch[f1^1],d-1,x,v);
return query(tr[o].ch[f1],d-1,x,v);
}
int main() {
scanf("%d", &n);
REP(i,1,n) {
int op, x, v;
scanf("%d%d", &op, &x);
if (op==1) add(T,sz,x,1);
else if (op==2) add(T,sz,x,-1);
else {
scanf("%d", &v);
printf("%d\n", query(T,sz,x,v));
}
}
}
练习2 : CF 842D Vitya and Strange Lesson
大意: 给定$n$个数的集合, 每次将集合中所有数异或$x$, 求整个集合的mex
#include <iostream> #include <algorithm> #include <math.h> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; const int N = 1<<19; int n, m, tot, T; int vis[N]; struct {int ch[2],sum;} tr[N<<2]; void ins(int &o, int d, int x) { if (!o) o=++tot; ++tr[o].sum; if (d>=0) ins(tr[o].ch[x>>d&1],d-1,x); } int query(int o, int d, int x) { if (d<0) return 0; int t = x>>d&1; if (tr[tr[o].ch[t]].sum==(1<<d)) return (1<<d)^query(tr[o].ch[t^1],d-1,x); return query(tr[o].ch[t],d-1,x); } int main() { scanf("%d%d", &n, &m); REP(i,1,n) { int t; scanf("%d", &t); if (vis[t]) continue; vis[t] = 1; ins(T,18,t); } int num = 0; REP(i,1,m) { int t; scanf("%d", &t); printf("%d\n", query(T,18,num^=t)); } }