Trie树
文章目录
- Trie树
- Trie字符串统计
- 正解
- 最大异或对
- 1.暴力 (可以过6/10个测试点)
- 2. Trie树模拟
用法:高效地存储和查找字符串集合的数据结构
存储形式: 将n个单词各个字符进行枚举,若是(根节点所指向包含字符c,那么直接相连接着写)
每个单词结尾处做一个标记,表示以当前节点结尾处有一个单词
Trie字符串统计
题目:
初步思路:想到用kmp来模拟暴力解,最终过去6/18个测试点
错误解法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int ne[N];
string str;
void getNext(char* t, int m) {
ne[0] = -1;
int j = 0, k = -1;
while(j < m) {
if(k == -1 || t[j] == t[k]) ne[++j] = ++k;
else k = ne[k];
}
}
int kmp(char* t, int n, int m) {
int i = 0, j = 0, r = 0;
while(i < n) {
if(j == -1 || str[i] == t[j]) i++, j++;
else j = ne[j];
}
if(j == m) r++, j = ne[j];
return r;
}
int main() {
int n;
char op, t[N];
cin >> n;
getchar();
while(n --) {
scanf("%c%s", &op, t);
getchar();
if(op == 'I') str += t;
else if(op == 'Q'){
int n = str.size();
int m = strlen(t);
getNext(t, m);
int res = kmp(t, n, m);
printf("%d\n", res);
}
}
return 0;
}
正解
用Trie树来存储字符串求解
#include <iostream>
using namespace std;
const int N = 100010;
//p代表节点,idx代表位置下标,cnt[p]代表以p节点结尾的所求字符串个数
int son[N][26]; //节点个数最多为N,只包含小写字母,故每个节点最多向往延伸出26条边
int cnt[N]; //以i节点结尾的字符串个数
int idx; // 各个节点的编号,根节点编号为0
char str[N];
void insert(char str[]) {
int p = 0; //指向根节点
for(int i = 0; str[i]; i++) {
int t = str[i] - 'a'; //将[a,z] 映射到 [0,25]
//如果数中不能走到当前字符
//为当前字符创建新的节点,保存该字符
if(!son[p][t]) son[p][t] = ++idx; 新节点编号为 idx + 1
p = son[p][t]; //p指向对应idx位置
}
cnt[p]++;
}
int query(char str[]) {
int p = 0;
for(int i = 0; str[i]; i++) {
int t = str[i] - 'a';
if(!son[p][t]) return 0;
//指向下一个节点
p = son[p][t];
}
return cnt[p];
}
int main() {
int n;
cin >> n;
while(n --) {
char op[2];
scanf("%s%s", op, str);
if(op[0] == 'I') insert(str);
else printf("%d\n", query(str));
}
return 0;
}
最大异或对
思路:
1.暴力 (可以过6/10个测试点)
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int main() {
int n;
cin >> n;
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
int res = 0;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
res = max(res, a[i]^a[j]);
cout << res;
return 0;
}
2. Trie树模拟
固定 A i A_i Ai,从 A 1 A_1 A1 ~ A n A_n An中选出最 A j A_j Aj,使得 A i ∗ A j A_i * A_j Ai∗Aj最大
A i A_i Ai = 11010101010…….(32位),尽量保证选区的数从左到右相异(这样可以保证结果为1,最大)
代码如下:
//用Trie树来解的话,最多有1e5个数,每个数最多有31位,故最多有1e5*31个节点(而且肯定比这小,有重复节点)
#include <iostream>
using namespace std;
const int N = 3000000, M = 100010;
int son[N][2]; //延伸出只有0/1两种选择
int a[N], idx;
void insert(int x) {
int p = 0;
for(int i = 30; i >= 0; i--) { //向右移动i位,从左往右第i位(从0开始)
if(!son[p][x >> i & 1]) son[p][x >> i & 1] = ++idx;
p = son[p][x >> i & 1];
}
}
int query(int x) {
int p = 0, res = 0;
for(int i = 30; i >= 0; i--) {
int u = x >> i & 1; //第i位
if(son[p][!u]) { //找到与u不同的位数节点,这样才能保证异或的答案最大(1)
res += 1 << i; //如果有这样的节点可以选择,那么答案的当前位置(第i位为1)
p = son[p][!u];
} else { //没有上边的更优的节点选择的话,那么退而求其次,当前位置就为0了,答案不用累加了也
p = son[p][u];
}
}
return res;
}
int main() {
int n;
cin >> n;
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
insert(a[i]);
}
int res = 0;
for(int i = 0; i < n; i++) res = max(res, query(a[i]));
cout << res;
return 0;
}