杭电多校2019-5B-Three arrays (01字典树解决异或问题)
There are three integer arrays a,b,c. The lengths of them are all N. You are given the full contents of a and b. And the elements in c is produced by following equation: c[i]=a[i] XOR b[i] where XOR is the bitwise exclusive or operation.
Now you can rearrange the order of elements in arrays a and b independently before generating the array c. We ask you to produce the lexicographically smallest possible array c after reordering a and b. Please output the resulting array c.
Input
The first line contains an integer T indicating there are T tests.
Each test consists of three lines. The first line contains one positive integer N denoting the length of arrays a,b,c. The second line describes the array a. The third line describes the array b.
-
T≤1000
-
1≤N≤105
-
integers in arrays a and b are in the range of [0,230).
-
at most 6 tests with N>100
Output
For each test, output a line containing N integers, representing the lexicographically smallest resulting array c.
Sample Input
1
3
3 2 1
4 5 6
题目大意:
给定两个数组 a和b,a,b数组里面元素的顺序是可以随意变化的。
c[i] = a[i]^b[i]
求一个字典序最小的c数组并输出
解题思路:
看到异或 就可以想到用01字典树
01字典树能解决区间内与某个数异或最大/小问题
建立两棵01字典树,分别插入a数组中的每个数,和b数组中的每个数。
然后开始遍历两棵字典树,并记录每个节点走过的次数,用num数组记录。
因为是01字典树 只是2叉的。
先让字典树都向相同的方向走。00 11 为什么这样呢,因为这样的异或是0 如果不能走相同的方向再走不同的方向,同时让ans的这个位变为1,因为这个位的异或值是1了。最后返回一个答案给c数组,最后把c数组排个序就是我们要的答案。
几个问题:
1 如何让ans的这个位变为1 ?
我们引进一个ss数组,ss[i]就是 第i位是1,其他位都是0.
这样我们走第i位,如果想让i位变成1 那就加上ss[i]即可。
for(int i=0;i<=30;i++){
ss[i] = 1<<i;
}
2 如何遍历字典树
因为是2叉,我们可以枚举出每棵树的左右子节点,看有没有。
int s10=tree1[p1][0],s11=tree1[p1][1];
int s20=tree2[p2][0],s21=tree2[p2][1];
3。咋解决超时问题
我写的时候,不停地T,一直找不出为啥,后来知道了
在初始化数组的时候,我们要初始化tree和num数组 不必全部初始化,只需要初始化被改变的前max(tot1,tot2)个就可以。
void init(){
int MAX=max(tot1,tot2);
for(int i=0;i<=MAX;i++){
tree1[i][0]=tree1[i][1]=0;
tree2[i][0]=tree2[i][1]=0;;
num[i][0]=0;
num[i][1]=0;
}
tot1=tot2=0;
cnt = 0;
}
(ps:膜拜的比我小一级的大佬的思路 我是真滴菜)
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+10;
int tree1[maxn*40][2];
int tree2[maxn*40][2];
int tot1=0,tot2=0;
int num[maxn*40][2];//每个结点的次数
int a[maxn],b[maxn];
int n;
int c[maxn];
inline void add(int x,int f){
int root=0;
int id;
if(f==1){
for(int i=30;i>=0;i--){
id = (x>>i)&1;
if(!tree1[root][id]) tree1[root][id] = ++tot1;
root = tree1[root][id];
num[root][0]++;
}
}else if(f==2){
for(int i=30;i>=0;i--){
id = (x>>i)&1;
if(!tree2[root][id]) tree2[root][id] = ++tot2;
root = tree2[root][id];
num[root][1]++;
}
}
}
int ss[40];
int cnt = 0;
void init(){
int MAX=max(tot1,tot2);
for(int i=0;i<=MAX;i++){
tree1[i][0]=tree1[i][1]=0;
tree2[i][0]=tree2[i][1]=0;;
num[i][0]=0;
num[i][1]=0;
}
tot1=tot2=0;
cnt = 0;
}
bool solve(){
int p1=0;
int p2=0;
int ans=0;
int i=0;
for( i=0;i<=30;i++){
int s10=tree1[p1][0],s11=tree1[p1][1];
int s20=tree2[p2][0],s21=tree2[p2][1];
if(s10&&s20){
if(num[s10][0]>0&&num[s20][1]>0){
num[s10][0]--;
num[s20][1]--;
p1=s10,p2=s20;
continue;
}
}
if(s11&&s21){
if(num[s11][0]>0&&num[s21][1]){
num[s11][0]--;
num[s21][1]--;
p1=s11,p2=s21;
continue;
}
}
if(s11&&s20){
if(num[s11][0]>0&&num[s20][1]>0){
num[s11][0]--;
num[s20][1]--;
p1=s11,p2=s20;
ans+=ss[30-i];
continue;
}
}
if(s10&&s21){
if(num[s10][0]>0&&num[s21][1]>0){
num[s10][0]--;
num[s21][1]--;
p1=s10,p2=s21;
ans+=ss[30-i];
continue;
}
}
break;
}
if(i<=30) return false;
c[++cnt]=ans;
return true;
}
int main(){
std::ios::sync_with_stdio(0);
for(int i=0;i<=30;i++){
ss[i] = 1<<i;
}
int t;
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
add(a[i],1);
}
for(int i=1;i<=n;i++){
cin>>b[i];
add(b[i],2);
}
while(solve()){
continue;
}
sort(c+1,c+1+cnt);
for(int i=1;i<=cnt;i++){
cout<<c[i];
if(i==cnt) cout<<endl;
else cout<<" ";
}
init();
}
return 0;
}