Trie树是字符串问题中应用极为广泛的一种数据结构,可以拓展出AC自动机、后缀字典树等实用数据结构。
然而在此我们考虑0-1 Trie的应用,即在序列最大异或问题中的应用。
这里的异或是指按位异或。按位异或有很多重要的性质。比如可拆分性,每个位可以进行单独处理后线性合并得到最终结果。
同时按位异或也是可减的。比如0111 ^ 1010 = 1101, 那么 1101 ^ 1010 = 0111. 证明从略。
首先我们考虑0-1 Trie的版本,也就是
给定一个序列a[i], 每次询问一个数x与a[i]中各元素能得到的按位异或的最大值。
暴力自然是O(n^2)的。但是我们想到之前的可拆分性,是否能将每个位单独考虑?但是,第一位的选择又会限定第二位的选择范围。即选择第一位是0或1后,第二位的选择就不能从a[i]中的所有元素中进行选择,而要将a[i]分为两份。我们很容易发现这是一个类似树形的问题,所以我们考虑使用树形数据结构。而鉴于多个串根据前缀进行选择性划分的特点,我们使用Trie树来从高位到低位地维护这些0-1串,即0-1 Trie。
注意到这种从高位到低位的选择一定是全局最优的。也就是说,对于异或结果,从高到低考虑,每一位能设成1就设成1. 证明可以利用反证法。
这样我们就利用一个贪心完成了这样的事情。这样的处理是O(n+m)的(常数有32倍)
什么时候需要使用可持久化0-1 Trie呢?我们想,在使用可持久化线段树维护区间K大值得时候,可持久化是否起到了限定区间的作用?同理,在这里,我们也是用可持久化来实现区间的限定。
重要性质:Trie树的节点存在性满足可减性。
我们可以把节点的存在性记录改为节点的数目记录,这样用root[r]中某节点的数目减去root[l-1]中某节点的数目,就可以得到区间中是否存在某个节点。
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 int ch[1000005][2],val[1000005],cnt[1000005],root[1000005],ts[105],ind,n,m; 5 6 void insert(int p,int p0,int dep) { 7 ch[p][0]=ch[p0][0]; 8 ch[p][1]=ch[p0][1]; 9 if(dep==30) return; 10 if(ch[p0][ts[dep+1]]==0) { 11 ch[p][ts[dep+1]]=++ind; 12 val[ind]=ts[dep+1]; 13 cnt[ind]=1; 14 insert(ind,ch[p0][ts[dep+1]],dep+1); 15 } 16 else { 17 ch[p][ts[dep+1]]=++ind; 18 val[ind]=ts[dep+1]; 19 cnt[ch[p][ts[dep+1]]]=cnt[ch[p0][ts[dep+1]]]+1; 20 insert(ch[p][ts[dep+1]],ch[p0][ts[dep+1]],dep+1); 21 } 22 } 23 24 void trie_insert(int rtx,int num) { 25 for(int i=1;i<=30;i++) 26 ts[i]=(num>>(30-i))&1; 27 insert(root[rtx],root[rtx-1],0); 28 } 29 30 int xormax(int rtx,int rty,int num) { 31 int p=root[rtx], q=root[rty], ans=0; 32 for(int i=29;i>=0;i--) { 33 if((num>>i)&1) { 34 if(cnt[ch[q][0]]-cnt[ch[p][0]]) ans=ans*2+1, p=ch[p][0], q=ch[q][0]; 35 else ans=ans*2, p=ch[p][1], q=ch[q][1]; 36 } 37 else { 38 if(cnt[ch[q][1]]-cnt[ch[p][1]]) ans=ans*2+1, p=ch[p][1], q=ch[q][1]; 39 else ans=ans*2, p=ch[p][0], q=ch[q][0]; 40 } 41 } 42 return ans; 43 } 44 45 int main() { 46 cin>>n; 47 for(int i=1;i<=n;i++) { 48 int t; 49 cin>>t; 50 root[i]=++ind; 51 trie_insert(i,t); 52 } 53 cin>>m; 54 for(int i=1;i<=m;i++) { 55 int t1,t2,t3; 56 cin>>t1>>t2>>t3; 57 t1--; 58 cout<<xormax(t1,t2,t3)<<endl; 59 } 60 }