CF842D Vitya and Strange Lesson
题意简述
给\(n\)个数\(a_1,a_2,\cdots,a_n\),有\(m\)个操作,每次操作给一个数\(x\),要求:
- 将所有数\(\operatorname{xor} \space x\)
- 求此时数列的\(\text{mex}\)值
\(\text{mex}\)指当前数列未出现的最小非负整数。
数据范围自行查看题目。
简单口胡
先考虑一下没有第一个\(\operatorname{xor}\)操作的时候怎么做。我们将所有数插入一个01-trie中,对于每一位我们都想:最好往0走。但是什么时候是不能往0走的呢?
如果左子树(即0节点的子树)是一个满二叉树,说明如果走到0节点,那么就没有不出现的数了,而我们要找的是\(\text{mex}\),违背定义,这时候只能退而求其次,走1节点。
加上了\(\operatorname{xor}\)其实也是差不多的,异或具有结合律,即\(a \operatorname{xor} x_1 \operatorname{xor} x_2 = a \operatorname{xor} (x_1 \operatorname{xor} x_2)\),所以记录\(s\)为当前的\(\operatorname{xor}\)和,如果第\(i\)位\(s\)为1,那么就是说明01-trie的第\(i\)层要取反,那么也就是最好往1走,如果是0那就是一样的。
写的话还是看水平,反正我写了好久/kk
# include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int n,m;
int a[N];
int s;
struct _01trie
{
int T[N << 2][2],siz[N << 2];
int tot = 0;
void insert(int x)
{
int root = 0;
for(int i = 20; i >= 0; i--)
{
int p = (x >> i) & 1;
if(T[root][p] == 0) T[root][p] = ++tot;
root = T[root][p];
}
return;
}
void dfs(int root)
{
if(T[root][0] == 0 && T[root][1] == 0)
{
siz[root] = 1;
return;
}
if(T[root][0]) dfs(T[root][0]);
if(T[root][1]) dfs(T[root][1]);
siz[root] += siz[T[root][0]] + siz[T[root][1]] + 1;
// printf("root = %d,siz[%d] = %d\n",root,root,siz[root]);
return;
}
int query(void)
{
int root = 0;
int ans = 0;
for(int i = 20; i >= 0; i--)
{
int p = (s >> i) & 1;
// if(p == 1)
// {
// printf("i = %d\n",i);
// printf("root = %d\n",root);
// printf("T = %d\n",T[root][p]);
// printf("siz = %d\n",siz[T[root][p]]);
// }
if(T[root][p] != 0 && siz[T[root][p]] == (1 << (i + 1)) - 1)
{
// printf("root = %d\n",root);
root = T[root][p ^ 1],ans += (1 << i);
}
else root = T[root][p];
if(!root) break;
}
return ans;
}
}Trie;
int main(void)
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
sort(a + 1,a + n + 1);
int Newn = unique(a + 1,a + n + 1) - a - 1;
for(int i = 1; i <= Newn; i++)
{
Trie.insert(a[i]);
}
Trie.dfs(0);
while(m--)
{
int x;
scanf("%d",&x);
s ^= x;
printf("%d\n",Trie.query());
}
return 0;
}