"trie"(字典树) 和 "异或" 的一些性质

目录

异或性质:

没有的性质:

构造完二进制数 后 查找 数字 x 的最大异或:

更进一步的 CodeForces - 1055F Tree and XOR 

(HDU6625  three arrays ————01 trie + 贪心)


异或性质:

  • 可以求前缀和, 我们想知道 a[L] ^ a[L+1] ^ ... ^ a[R] 可以用一个 xor数组记录 , xor[i] = a[1] ^ a[2] ^ ... ^ a[i]           所以 [L , R] 区间的 异或和 是 xor[R] ^ x[L-1]

(BZOJ 4260如果一个数组a , 求一段区间内  的 最大值 异或和 , 求出前缀异或和 ,建立 01 字典树 ,一个一个往字典树里insert , 插入一个xor[i] 之前, 先在字典树里查最大的异或值,就能O(N)的求出 区间异或 最大值(最小值)

(POJ 3764树上有边权 , 任意两个点 u , v 两点间路径的最大异或和 f[u,v], 由这个性质 可以处理出 v[i] = f[1,i] 的值 , 然后用这个数组 f[u , v] = f[1,u] ^ f[1,v] , 那么就可以用上面那个题的方式 求出max的答案

  •  一堆数异或可以通过异或 , 消除自身 , 因为 Ai ^ Ai = 0 

(HDU 5536求 (a[i] + a[j]) ^ a[k] 的最大值 , 可以枚举 i , j ,并把它们从字典树里删除 , 然后 在字典树里 跑 (a[i] + a[j]) 的最大值 (数据满足O(N ^ 2) 的 复杂度)

思考怎么删除某个a[i] : 可以建树时增加一个标记 w , 每个节点访问一次 w++ , 删除就是字典树里跑这个数字 w--;

计算答案的时候如果这个节点w == 0 则此节点不能访问

 

没有的性质:

异或不满足结合律,即 (a[i] + a[j]) ^ a[k] != a[i] ^ a[k] + a[j] ^ a[k]

 

构造完二进制数 后 查找 数字 x 的最大异或:

看一个数 x 的二进制,从前往后数 第i为是 0 还是 1 , for(i = 61 ~ 0)直接 bool   tag = ((x << i) & 1) ,  tag 就是第 i 位 的 二进制数 , 然后在字典树上跑 即可。

 

更进一步的 CodeForces - 1055F Tree and XOR 


给一棵 N个点(N <= 1e6)的 树 , 每条边上有边权,任意两点 u , v (u 可以等于 v ,且 {u,v} != {v,  u}  ,所以有 N^2 个值 ) 之间 路径异或 后 有个值 ,问你这些值 sort 后 第k大是多少。

  1. 首先我们可以 进行 处理出来 从root节点(1节点) 按路径异或到 其他节点的所有值 F[ 1 , i ] ,w[i] 数组保存 F[ 1 , i ]
  2. w[i] 数组建立字典树,但是因为 N <= 1e6 我们发现空间复杂度不够,所以就要用两个滚动数组G1[] , G2[] , 来滚动更新字典树中的每一层,这样就节省了空间G1[] 是字典树上 递推出来的新一层  , G2[] 是保存上一层答案 算答案的节点。因为root 节点是 1 , 所以全部初始化 1
  3. 我们通过上一层字典树节点 计算出的 新层G1[] , 可以算得当前枚举的第 i 位通过 异或 操作可以得 0 的个数,                     因为 0 ^ 0 = 0 , 1 ^ 1 = 0 , 所以直接通过计算1 …N 的 sum[1] + sum[0] 就可以,计算出 ans中 此位贪心得 0 的个数(sum)
  4. 如果sum < K 则 表示 说明下一位填0时不够K个 , 所以 K -= sum ,此位填 1 (Now = 1) , 反之 (Now = 0)
  5. 最通过 Now 和 G1[]层选的值 , 来更新G2[]数组
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e6 + 7;
const int Inf = 1e9 + 7;

int N;
LL K , ans , sum , tot , Now;
LL w[Maxn];
int G1[Maxn] , G2[Maxn];//滚动数组
int Level[Maxn][2];
int sz[Maxn];
void init(){
	for(LL i = 1 ; i <= tot ; i++){
		Level[i][0] = Level[i][1] = sz[i] = 0;
	}
	tot = sum = Now = 0;
}
int GetId(int nod , int bt){
	if(!Level[nod][bt])	Level[nod][bt] = ++tot;
	return Level[nod][bt];
}
int main()
{
	scanf(" %d %lld",&N,&K);
	for(int i = 2 ; i <= N ; i++){
		int pre; scanf(" %d %lld",&pre,&w[i]);
		w[i] = w[i] ^ w[pre];
	}
	for(int i = 1 ; i <= N ; i++)	G1[i] = G2[i] = 1;
	for(int i = 62 ; i >= 0 ; --i){
		init();
		for(int j = 1 ; j <= N ; ++j){
			G1[j] = GetId(G1[j] , (w[j]>>i) & 1);
			sz[G1[j]]++;
		}
		for(int j = 1 ; j <= N ; ++j){
			sum += sz[ Level[ G2[j] ][(w[j]>>i)&1] ];
		}
		if(sum < K){
			K -= sum;
			Now = 1;
			ans += (1LL) << i;
		}
		for(int j = 1 ; j <= N ; ++j){
			G2[j] = Level[G2[j]][ ((w[j]>>i)&1) ^ Now ];
		}
	}
	printf("%lld\n",ans);
	return 0;
}

此题虽然计算出了第K大,单数有一些局限性,他的第K大中包含 u, v 的 F[u ,u] , F[u , v] != F[v , u],如果题目出现 u != v

和 F[u , v] = F[v , u] ,则此方法不能计算答案。

 

HDU6625  three arrays ————01 trie + 贪心

题意:给两个数组 a[] , b[] , 数组长度 N (N <= 1e5) 两个数组可以 在本数组内重排 ,问重排后对应位置 ai ^ bi = ci

让 c[] 数组字典序最小 ,求c数组。

两个数组分别建01 trie , 同时DFS贪心,如果一层 有同 0 或 同 1,就先这么走,如果没有此位必定 是 1,搜到结束删除这条链,如此反复N次,得到N个答案,sort以下输出即可。

 

思考:字典树 + XOR  求最大值,最小值 和 这种字典序等问题都离不开贪心思想。贪心的让当前位最大。查完结果可以将这个链删除,就可以贪心出答案。所以我们下次trie题目都要思考怎么贪心保证最大解,这个建两个01 trie 的思想就非常不错,不能限制思维,只在一个 trie 上搞

 

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e5 + 7;
const int Inf = 1e9 + 7;
const int Bit = 30;
int trie1[Maxn * 30][2] , trie2[Maxn * 30][2];
int sum1[Maxn * 30 + 5] , sum2[Maxn * 30 + 5];
int tot1 , tot2;
int N;

void init(){
	tot1 = tot2 = 0;
}
void Insert(int x , bool flag){
	int bt;
	int root = 0;
	for(int i = Bit ; i >= 0 ; i--){
		bt = (x >> i) & 1;
		if(!flag){
			if(!trie1[root][bt]) trie1[root][bt] = ++tot1;
			sum1[trie1[root][bt]]++;
			root = trie1[root][bt];
		} else {
			if(!trie2[root][bt]) trie2[root][bt] = ++tot2;
			sum2[trie2[root][bt]]++;
			root = trie2[root][bt];
		}
	}
}
void renit(int num , int ct, bool flag){
	if(!flag)	trie1[num][ct] = 0;
	else	trie2[num][ct] = 0;
}
int solve(){
	int res = 0;
	int root1 , root2;	root1 = root2 = 0;
	for(int i = Bit ; i >= 0 ; i--){
		int t1_0 = trie1[root1][0] , t1_1 = trie1[root1][1];
		int t2_0 = trie2[root2][0] , t2_1 = trie2[root2][1];
		if(t1_0 && t2_0){
			--sum1[t1_0];
			--sum2[t2_0];
			if(!sum1[t1_0]) renit(root1 , 0 , 0);
			if(!sum2[t2_0]) renit(root2 , 0 , 1);
			root1 = t1_0;
			root2 = t2_0;
		} else if(t1_1 && t2_1) {
			--sum1[t1_1];
			--sum2[t2_1];
			if(!sum1[t1_1]) renit(root1 , 1 , 0);
			if(!sum2[t2_1]) renit(root2 , 1 , 1);
			root1 = t1_1;
			root2 = t2_1;
		} else if(t1_0 && t2_1) {
			--sum1[t1_0];
			--sum2[t2_1];
			if(!sum1[t1_0]) renit(root1 , 0 , 0);
			if(!sum2[t2_1]) renit(root2 , 1 , 1);
			root1 = t1_0;
			root2 = t2_1;
			res += (1 << i);
		} else if(t1_1 && t2_0) {
			--sum1[t1_1];
			--sum2[t2_0];
			if(!sum1[t1_1]) renit(root1 , 1 , 0);
			if(!sum2[t2_0]) renit(root2 , 0 , 1);
			root1 = t1_1;
			root2 = t2_0;
			res += (1 << i);
		}
	}
	return res;
}
int ans[Maxn];
int main()
{
	int T; scanf(" %d",&T);
	while(T--){
		init();
		scanf(" %d",&N);
		int x;
		for(int i = 1 ; i <= N ; i++){
			scanf(" %d",&x);
			Insert(x , 0);
		}
		for(int i = 1 ; i <= N ; i++){
			scanf(" %d",&x);
			Insert(x , 1);
		}
		for(int i = 1 ; i <= N ; i++) ans[i] = solve();
		sort(ans + 1 , ans + N + 1);
		for(int i = 1 ; i <= N ; i++){
			printf("%d",ans[i]);
			if(i != N)	printf(" ");
		}
		printf("\n");
	}
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值