大家都用过搜索引擎吧,在输入关键字时,下面的提示框会不断快速变化,里面的内容是一些以目前输入关键字开头的搜索记录,而且我们每次的搜索也会被加入到数据库中。
这个功能是怎么实现的呢?总不能遍历整个数据库呀,请看下文Trie树。
Trie树的基本功能是在支持插入、删除的情况下动态维护以一个前缀开头的所有字符串,那其存储有何特点?
刚才的叙述可能有些不好理解,让我们来看一个具体的案例,如图为以字符串"az",“app”,“apple”,“iak”,“ice”,“iakioi”,"iaknoi"构造的Trie树:
为方便实现,假设所有字符串均由小写字母构成。
初始化
令 N N N为需要的结点个数(即所有字符串长度之和),令 L L L为每个结点最大子结点数(即字符串每位有多少种可能)。
const int N = 1e5 + 5, L = 26;
struct Trie {
int t[N][L], cnt[N], k;
void init() {
memset(t, -1, sizeof t);
memset(cnt, 0, sizeof cnt);
k = 0;
}
};
插入
在树中找到其前缀的链,若不能完全包含这个字符串则新建剩下的结点。
void insert(char *s) {
int u = 0; // u: 当前结点
for (int i = 0; s[i]; ++i) { // 寻找前缀链
int &v = t[u][s[i] ^ 'a']; // 下一位结点
if (v == -1) { // 若没有:新建
v = ++k;
}
u = v; // 迭代
}
++cnt[u]; // 记录结束位置
}
删除
同理,沿链寻找其结束结点,将此位置结束的字符串数 − 1 -1 −1即可。
void erase(char *s) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int v = t[u][s[i] ^ 'a'];
if (v == -1) {
return ;
}
u = v;
}
if (cnt[u]) {
--cnt[u];
}
}
查询
同理,返回:此字符串出现次数。
int find(char *s) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int v = t[u][s[i] ^ 'a'];
if (v == -1) {
return 0;
}
u = v;
}
return cnt[u];
}
最终代码
const int N = 1e5 + 5, L = 26;
struct Trie {
int t[N][L], cnt[N], k;
void init() {
memset(t, -1, sizeof t);
memset(cnt, 0, sizeof cnt);
k = 0;
}
void insert(char *s) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int &v = t[u][s[i] ^ 'a'];
if (v == -1) {
v = ++k;
}
u = v;
}
++cnt[u];
}
int find(char *s) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int v = t[u][s[i] ^ 'a'];
if (v == -1) {
return 0;
}
u = v;
}
return cnt[u];
}
void erase(char *s) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int v = t[u][s[i] ^ 'a'];
if (v == -1) {
return ;
}
u = v;
}
if (cnt[u]) {
--cnt[u];
}
}
};
题意:给定一个数组 a 1... n a_{1...n} a1...n,求 m a x { a i x o r a j } max\{a_i xor a_j\} max{aixoraj}。
题解:考虑在线处理,求出目前值和之前值的最大异或再取最大,如何求出?因为异或为二进制运算,则从前往后每次考虑当前位最优,即如果有的话就取目前二进制位的取反,这里就需要Trie的维护,只不过存储的不是字符串而是二进制位。
题意:给定一个字符集 S S S和一个字符串 T T T,求用 S S S中字符串拼接成 T T T的方案数。
题解:很容易想到dp,定义 f i f_i fi为 T T T前 i i i位的答案;递推式也显而易见,若 T j . . . i T_{j...i} Tj...i在 S S S中出现过, f i + = f j − 1 f_i+=f_j-1 fi+=fj−1,这里是否出现就需要用Trie维护。
题意:给定字符集,求其中任意两个字符串最长公共前缀的期望值。
题解:我们知道 E = ∑ P × X E=\sum{P\times X} E=∑P×X,所以我们可以在以此字符集建立的Trie中枚举当前点 u u u是最长公共前缀结尾的情况数再除以总情况数即可。对于以 u u u为结尾的情况,我们分三种情况讨论:第一种,两个串的结尾在 u u u两个子树中;第二种,一个以 u u u结尾,另一个以其子树中点结尾;第三种,均以 u u u结尾。
今天的复习内容就到这里,感谢收看~