2019杭电暑期多校第五场B - three arrays (01字典树/字典树跑字典树)

以下均学习与此博客https://blog.csdn.net/mmk27_word/article/details/98611040

-----------------------------------------------

题意:该你两个数组,问你这两个数组重新排列后a[i]^b[i]=c[i],让你按字典序最小输出c[i];

题解:对于一个给定数组和一个数,问这个数在这个数组中和某一个数的最大异或值/最小异或值,就是01字典树

  1. 这个题需要建两颗01字典树,查询的时候按照贪心的策略:
  2. 优先跑同一位置下相同的情况(即同一边),该位异或为0,其次在跑不同情况,加上改位的值(2^i)两颗字典树在同一位置,都是0和1两个分叉。
  3. 因为跑的时候不一定每次是最优的,结果需排列一下。

细节:每跑到一个节点都需改节点访问次数-1,因为val记录的是建树的时候这个节点被走过的次数,也就是说有多少个数字的二进制的这一位是相同的个数,走过就删除该数的意思,用过就不能再用了

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 5;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
struct node{
	int nx[2];//连接两个子节点边的信息 
	int val;//点的信息 
}T[2][maxn*32];
int tot[2];
int ans[maxn];
void init(){//初始化 
	tot[0]=tot[1]=1;
	T[0][0].nx[0]=T[0][0].nx[1]=T[0][0].val=0;
	T[1][0].nx[0]=T[1][0].nx[1]=T[1][0].val=0;
}
void insert(int x,int op){
	int u=0;//根结点 
	for(int i=31;i>=0;i--){
		int v=((x>>i)&1);//从高往低枚举当前位是0还是1 
		if(!T[op][u].nx[v]){//没有编号,创建结点
			T[op][tot[op]].nx[0]=T[op][tot[op]].nx[1]=0;//初始化该结点的子结点
			T[op][tot[op]].val=0;
			T[op][u].nx[v]=tot[op]++; //将tot号作为新结点的编号
		}
		u=T[op][u].nx[v];//遍历到u的第V个儿子结点 
		T[op][u].val++;//访问次数+1 
	}
}
int query(){
	int p0=0,p1=0;//首结点 
	int x=0;
	for(int i=31;i>=0;i--){//优先走同一位置相同的情况 
		if(T[0][T[0][p0].nx[0]].val&&T[1][T[1][p1].nx[0]].val){
			p0=T[0][p0].nx[0];
			T[0][p0].val--;
			p1=T[1][p1].nx[0];
			T[1][p1].val--;
		}else if(T[0][T[0][p0].nx[1]].val&&T[1][T[1][p1].nx[1]].val){
			p0=T[0][p0].nx[1];
			T[0][p0].val--;
			p1=T[1][p1].nx[1];
			T[1][p1].val--;
		}else if(T[0][T[0][p0].nx[1]].val&&T[1][T[1][p1].nx[0]].val){
			p0=T[0][p0].nx[1];
			T[0][p0].val--;
			p1=T[1][p1].nx[0];
			T[1][p1].val--;
			x+=(1<<i);//该位的(2^i) 
		}else if(T[0][T[0][p0].nx[0]].val&&T[1][T[1][p1].nx[1]].val){
			p0=T[0][p0].nx[0];
			T[0][p0].val--;
			p1=T[1][p1].nx[1];
			T[1][p1].val--;
			x+=(1<<i);//该位的(2^i) 
		}		
	}
	return x;
}
int main(){
	std::ios::sync_with_stdio(false);
	int t; cin>>t;
	while(t--){
		init();
		int n; cin>>n;
		for(int i=1;i<=n;i++){
			int x; cin>>x;
			insert(x,0);
		}
		for(int i=1;i<=n;i++){
			int x; cin>>x;
			insert(x,1);
		}
		for(int i=1;i<=n;i++){
			ans[i]=query();
		}
		sort(ans+1,ans+n+1);
		for(int i=1;i<=n;i++){
			cout<<ans[i];
			if(i<n) cout<<" ";
			else cout<<endl;
		}
	}
	
	return 0;
} 

 

         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值