Trie树:
Tire树可以较为高效的存储和查找字符串集合。
存储方式:
首先Trie有一个根节点,我们按从前往后的顺序将字符串的每个位置上的字符存储到树的每一层中,这样我们存储多个字符串时就可以消除一部分冗余,在查询字符串的时候时间复杂度是线性的,只与字符串的长度关联(层数)。
插入和查询字符串的代码:
//初始化
//所有字符串长度之和小于1e5
//son表示树最多N个节点,每个节点有26个儿子(26个小写字母)
//cnt表示以每个节点结尾的字符串有多少个
//idx代表当前用到哪个节点了,根节点为0号节点也是空节点
const int N = 1e5 + 10;
int son[N][26], cnt[N], idx = 0;
//插入
void insert(string str) {
int p = 0;//从根节点开始
for (int i = 0; str[i]; i++) {//遍历字符串,字符串以0结尾
if (!son[p][str[i] - 'a']) {//如果该节点不存在str[i]这个节点
son[p][str[i] - 'a'] = ++idx;
p = son[p][str[i] - 'a'];
}
else p = son[p][str[i] - 'a'];
}
cnt[p]++;
return;
}
//查询
int query(string str) {
int p = 0;//从根节点开始
for (int i = 0; str[i]; i++) {//遍历字符串,字符串以0结尾
if (!son[p][str[i] - 'a']) {//如果该节点不存在str[i]这个节点
return 0;
}
else p = son[p][str[i] - 'a'];
}
return cnt[p];
}
一些变量的说明:
son[N][26]代表了一共N个节点,而不是N层,26代表每个节点有26个儿子。遍历到下一层体现在数组中可能是跳跃了n个位置。cnt[N]代表N个节点,以每个节点为结束的字符串数量。根节点标号为0,也就是指向根节点的指针为0。
例题:最大异或对
在给定的 N个整数 A1,A2……AN中选出两个进行 xor(异或)运算,得到的结果最大是多少?
数据范围:1≤N≤1e5,0≤An≤2e31
输入格式:第一行一个数N,第二行N个数An。
输出格式:输出一个数表示结果。
思路:
异或运算:两个数的异或运算,先把两个数看最二进制形式,如果二进制位相同该二进制位就是0,不相同该二进制位就是1。
5^8:bin(5)=0101,bin(8)=1000,bin(5^8)=1101=dec(13)。
首先,如果使用暴力做法的话,双重循环,枚举每一对数,时间复杂度为O(n^2),对于1e5的数据会超时。Trie树做法:我们将每个数的每个二进制位放在一起构造出一颗Trie树(高位在前),然后我们只需要遍历每一个数,并将每一个数在树中做一次查找(尽可能的查找反方向的),这样就可以得出每个数的最大异或是多少。由于树的高度是二进制位的,二进制位的个数,所以时间复杂度为O(n*30),为线性的。
代码如下:
#include<iostream>
#include<cmath>
using namespace std;
const int N=1e5+10;
int son[N*31][2],idx=1;//每个数最多31个节点,一共1e5个数
int q[N];
int n;
void insert(int x){
int p=0;
for(int i=30;i>=0;i--){//从高位开始建立trie
if(!son[p][(x>>i&1)]){
son[p][x>>i&1]=idx++;
p=son[p][x>>i&1];
}
else p=son[p][x>>i&1];
}
return;
}
int query(int x){
int p=0;
long long res=0;
for(int i=30;i>=0;i--){
if(son[p][!(x>>i&1)]){res=res*2+1;p=son[p][!(x>>i&1)];}
else {res=res*2;p=son[p][x>>i&1];}
}
return res;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){//建立trie
cin>>q[i];
insert(q[i]);
}
int res=-0x3f3f3f3f;
for(int i=0;i<n;i++)
res=max(res,query(q[i]));
cout<<res;
return 0;
}