字典树
字典树(Trie)
先看一张图(来自 oi-wiki):

我们让每条边都代表一个字母,所以从根结点到任意一个结点的路径都是一个字符串。
因此,我们需要一个数组来记录每个结点是否有某条代表字母 \(c\) 的边,如果没有,就加上这条边,否则,直接跳到这条边指向的结点。
查找字符串
查询某个字符串是否出现过。
Trie 的时间复杂度为 \(O(n)\),\(n\) 代表所有字符串的字符总数。
暴力
把所有字符串存到 map 或 set 中,判断是否出现过。
(其实好像不算暴力)
正解
对所有字符串建 Trie,再在 Trie 中查询。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, f[N][30], c;
bool v[N], vis[N];
void Insert(string s) {
int root = 0;
for (int i = 0; i < s.size(); i++) {
if (!f[root][s[i] - 'a']) {
f[root][s[i] - 'a'] = ++c;
}
root = f[root][s[i] - 'a'];
}
v[root] = 1; // 从根结点到 root 的路径是一个字符串
}
void Find(string s) {
int root = 0;
for (int i = 0; i < s.size(); i++) {
root = f[root][s[i] - 'a'];
if (!root) { // 找不到了
cout << "WRONG\n";
return ;
}
}
if (!v[root]) {
cout << "WRONG\n";
} else {
cout << "OK\n";
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
while (n--) {
string s;
cin >> s, Insert(s);
}
cin >> m;
while (m--) {
string s;
cin >> s, Find(s);
}
return 0;
}
同样的,我们可以用字典树查找当前字符串是不是某个字符串的前缀。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e6 + 10;
int T, n, q, c, sz[N], f[N][75];
int ID(char a) {
return 'a' <= a && a <= 'z' ? a - 'a' : ('A' <= a && a <= 'Z' ? 26 + a - 'A' : 52 + a - '0');
}
void Insert(string s) {
int root = 0;
for (int i = 0; i < s.size(); i++) {
int t = ID(s[i]);
if (!f[root][t]) {
f[root][t] = ++c;
}
root = f[root][t], sz[root]++; // sz 表示从跟结点到 root 是这条路径是多少个字符串的前缀
}
}
void Find(string s) {
int root = 0;
for (int i = 0; i < s.size(); i++) {
root = f[root][ID(s[i])];
if (!root) {
break;
}
}
cout << sz[root] << '\n';
}
void Solve() {
cin >> n >> q, c = 0;
while (n--) {
string s;
cin >> s, Insert(s);
}
while (q--) {
string s;
cin >> s, Find(s);
}
for (int i = 0; i <= c; i++) {
for (int j = 0; j < 75; j++) {
f[i][j] = 0;
}
sz[i] = 0;
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> T;
while (T--) {
Solve();
}
return 0;
}

浙公网安备 33010602011771号