又称字典树(踹树)
作用:用来快速的存储和查找字符串集合的数据结构
本质根据字符串的每个字符作为节点建树
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
struct trie{
int son[N][26]; //一般题目都是全小写或者全大写或者全数字或者只有0 1
// 因为这里假设都是小写字母,所以最多只有26条边
int cnt[N]; // 以当前这个单词结尾的有多少个
int idx; // 单链表用了哪一个点,下标是0的点,既是根节点,又是空节点
void insert(char str[]){
int p = 0; // 从根节点开始
for(int i = 0; str[i]; i++){
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
cnt[p] ++; // 最后一定是到了最后一个单词,以这个节点结尾的单词数++
}
int query(char str[]){ // 返回这个单词出现了多少次
int p = 0;
for(int i = 0; str[i]; i++){
int u = str[i] - 'a';
if(!son[p][u]) return 0; // 这里不需要建边,只是查询
p = son[p][u];
}
return cnt[p];
}
}t;
int n;
char s[N];
int main(){
cin >> n;
while(n--){
char op[2];
scanf("%s%s", op, s);
if(op[0] == 'I') t.insert(s);
else cout << t.query(s) << endl;
}
return 0;
}
最大异或对
由异或的性质我们可以知道,如果一个数固定了(异或运算满足交换律),那么最高位于待定的数一定不同才能保证异或起来比较大
所以假设当前数最高位k位,那么把序列分成两部分:所有k位为0的数和所有k位为1的数;那么所有k位与该数的第k位不同的数与该数异或起来一定是比另一个集合要大的
或者某种分支他是不存在的,那么也就有唯一一条路径
这样每次的操作都保证了每一次的筛选都将是每一个集合中最大的那些,最后也就固定了哪个数是什么
这样画图来看,就特别像trie树,所以我们可以根据二进制位来对数进行存储
我们可以边插入边查找,不用一次都插入再查找;因为异或满足交换律,所以说我们边插入边查找也可以把每个数都判断一遍
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int son[31 * N][2], idx;
// 每个数最多有31位,一个有N个数,一共需要31 * N个节点
void insert(int x){
int p = 0;
for (int i = 30; i >= 0; i--){
int u = x >> i & 1;
if(!son[p][u])
son[p][u] = ++idx;
p = son[p][u];
}
}
int query(int x){
int p = 0;
int res = 0; // 记录路径上的数是什么
for (int i = 30; i >= 0; i--){
int u = x >> i & 1;
if(son[p][!u]){ // 如果另一个方向存在,肯定走使答案变大的那个方向
p = son[p][!u];
res = (res << 1) + !u;
}
else{
p = son[p][u];
res = (res << 1) + u;
}
}
return res;
}
int main(){
cin >> n;
for (int i = 1; i <= n; i++){
cin >> a[i];
}
int ans = 0;
for (int i = 1; i <= n; i++){
insert(a[i]);
int t = query(a[i]);
ans = max(ans, a[i] ^ t);
}
cout << ans;
return 0;
}