目录
更进一步的 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大是多少。
- 首先我们可以 进行 处理出来 从root节点(1节点) 按路径异或到 其他节点的所有值 F[ 1 , i ] ,用 w[i] 数组保存 F[ 1 , i ]
- 用 w[i] 数组建立字典树,但是因为 N <= 1e6 我们发现空间复杂度不够,所以就要用两个滚动数组G1[] , G2[] , 来滚动更新字典树中的每一层,这样就节省了空间,G1[] 是字典树上 递推出来的新一层 , G2[] 是保存上一层答案 算答案的节点。因为root 节点是 1 , 所以全部初始化为 1
- 我们通过上一层字典树节点 计算出的 新层G1[] , 可以算得当前枚举的第 i 位通过 异或 操作可以得 0 的个数, 因为 0 ^ 0 = 0 , 1 ^ 1 = 0 , 所以直接通过计算1 …N 的 sum[1] + sum[0] 就可以,计算出 ans中 此位贪心得 0 的个数(sum)
- 如果sum < K 则 表示 说明下一位填0时不够K个 , 所以 K -= sum ,此位填 1 (Now = 1) , 反之 (Now = 0)
- 最通过 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");
}
}