「01trie 贪心」题解 CF923C 【Perfect Security】

先无良宣传一下博客wwwwww
文章列表 - 核融合炉心 - 洛谷博客


知识点:Trie树/字典树/贪心


题意:

(简略版的题干太鬼畜了)

\(a,b\) 两长度为 \(n\) 的数列 ,
\(b\) 数列进行重新排列,
然后使 \(a,b\) 对应位置的数相 \(\underline{\text{异或}}\) ,
得到一个新数列\(c\)

求能使新数列 \(c\) \(\underline{\text{字典序最小}}\) 的方案
并输出此方案的结果

\(1\leqslant N \leqslant 3e5\)
\(\underline{a[i],b[i] \leqslant 2^{30}}\)


分析题意 :

  • 字典序最小 :
    \(c\) 数列的各位置 \(c[i]\) , 都应该是能达到的最小的值
    就可以考虑贪心

  • 异或 , \(a[i],b[i] \leqslant 2^{30}\) :
    为什么数据范围要写成这个样子? 再考虑异或运算...
    想到进行二进制拆分

    \(a[i],b[i]\) 拆分后 , 贪心就可以方便地进行
    枚举两 \(01\) 串上 每一位上的数 , 尽量使 各位 相异 或后为 \(0\)
    即可选择出最合适的 \(b[i]\)

  • 用什么来维护 拆分后的二进制数 ?
    可以将二进制数 , 当成字符串来处理
    想到建立一棵字典树
    由于只有 \(0,1\) 两字符,所以是一棵二叉树
    根据 字典树 的性质 , 可以很方便地完成上述操作 .


算法实现:

先建立一棵基于各 \(b[i]\) 的二进制数 的字典树
并记录 各节点代表的 元素出现的个数 \(cnt\)

每次查询操作 , 将 \(a[i]\) 作参数进行传递
从字典树根部 , 开始向下, 查找每一层 (即每一二进制位) 的元素 ,
都尽量使 \(a[i],b[i]\) 此二进制位相异或为 \(0\)

若最优元素在此位置个数为 \(0\) ,选择另一元素
使此位置选择的元素数量 \(-1\)
并进入下一层 , 继续进行查找 .

根据字典树的性质 ,
每次查询操作 必然可以找到 一个满足局部最优的 , 最适合 \(a[i]\)\(b[i]\)
查询 \(n\) 次后 , 即可找到最优的答案


注意的点:

为方便运算 ,
要保证 字典树 中 ,二进制数按照正向存储

也就是说,
如果使用位运算符进行二进制拆分, 要先拆出高位 , 再拆出低位
详见代码


附上 奇丑 的代码:

#include<cstdio>
#include<ctype.h>
#include<algorithm>
#define int long long
//==================================================
const int MARX = 3e5+10;
struct node
{
	int cnt,son[2];//存每个节点代表元素出现次数 , 两儿子 
}trie[20*MARX];
int n,num=1;
int a[MARX],b[MARX];//原始数列 
//==================================================
inline int read()
{
	int fl=1,w=0;char ch=getchar();
	while(!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') fl=-1;
	while(isdigit(ch)){w=w*10+ch-'0',ch=getchar();}
	return fl*w;
}
inline void insert(int key)//建树 
{
	int now=1;//从根节点开始,向下查找 
	trie[1].cnt++;
	for(int i=30;i>=0;i--)//将二进制数正向加入字典树 
	{
		int next=(key>>i)&1; //当前位数 
		if(!trie[now].son[next]) trie[now].son[next]= ++num;//添加新节点 
		now=trie[now].son[next];
		trie[now].cnt++;//增加出现次数 
	}
}
inline int query(int key)//回答询问 
{
	int now=1,ans=0;//从根部查找 
	for(int i=30;i>=0;i--) 
	{
		int next=(key>>i)&1;//最优情况 
		if(!trie[trie[now].son[next]].cnt) next=(!next);//不能满足最优情况 
		ans=(ans<<1)+next;//更新找到的 b[i] 
		now=trie[now].son[next];
		trie[now].cnt--;//减少出现次数 
	}
	return ans^key;
}
//==================================================
signed main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) b[i]=read(),insert(b[i]); 
	for(int i=1;i<=n;i++) printf("%lld ",query(a[i]));
}
posted @ 2019-09-03 23:03  Luckyblock  阅读(447)  评论(0编辑  收藏  举报