前提小知识:
因为很菜,所以我不知道
a^b=c
可逆
如果已知a^b=c
则c^b=a
c^a=b
题意:
给n个数m次询问 每次询问一个s 问:n个数中,哪个数异或s最大
一般解法:
普通解法:遍历m遍,每次循环n,然后利用比较得最大
时间复杂度:O(n*m)
前缀树解法:
建树:插入操作是
O(n)
的复杂度,查询操作是O(32)
的复杂度,故查询是O(1)
,时间复杂度为O(n+m)
,故为O(n)
很显然这道题
O(n*m)
的时间复杂度过不去,需要用O(n)
的时间复杂度,所以神奇的01字典树出现了
题解:
首先:我们如何利用前缀性,去造一颗字典树,对于数字,我们可以想到,把数字改成一串二进制数不就
ok
了,对于一串数字,我们可以利用其前缀性,造一颗字典树。造出字典树后,我们需要写一个贪心去查找,这里就用到a^b=c可逆
贪心思路:
如果我们让异或尽可能大,则最优情况是每一位都为1,由于最大位数是32位,所以我们最优情况是32个1,那么我们可以由a^b=1,由于其可逆性,得出字典树上是否存在a,a=b^1
,如果存在就转为这个节点,如果不存在,就用当前节点即可,直到查询到最后的节点,返回值即可
注意:要开long long 因为 int形不存在>>32
如果用int >>31 左移31即可
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int tree[32*maxn][2];//儿子只有两种类型的
int max_num[32*maxn];
int tot;
void insert_num(int k)
{
int root=0;
for(int i=31; i>=0; i--)
{
int id=(k>>i)&1;
if(!tree[root][id])
tree[root][id]=++tot;
root=tree[root][id];
}
max_num[root]=k;
}
int find_num(int s)
{
int root=0;
for(int i=31; i>=0; i--)
{
int id=(s>>i)&1;
if(tree[root][id^1])
root=tree[root][id^1];
else
{
root=tree[root][id];
}
}
return max_num[root];
}
int main()
{
int n,m,t,d=0;
scanf("%d",&t);
while(t--)
{
memset(tree,0,sizeof(tree));
//memset(max_num,0,sizeof(max_num));
scanf("%d %d",&n,&m);
//tot=0;
for(int i=1; i<=n; i++)
{
int x;
scanf("%d",&x);
insert_num(x);
}
// printf("%d\n",find_num(5));
printf("Case #%d:\n",++d);
while(m--)
{
int y;
scanf("%d",&y);
printf("%d\n",find_num(y));
}
}
}