Tire树(字典树)
理论
上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。从上图可以归纳出Trie树的基本性质:
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
- 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符互不相同。
- 从第一字符开始有连续重复的字符只占用一个节点,比如上面的to,和ten,中重复的单词t只占用了一个节点。
模板
//节点
struct Node {
//每个节点有26个子节点
Node* son[26]{};
//末尾标记,可用于区分前缀字符串和完整字符串
bool end = false;
};
class Tire {
Node* root = new Node();
int find(string s) {
Node* cur = root;
for (char c : s) {
int t = c - 'a';
if (cur->son[t] == nullptr) {
//没找到返回0
return 0;
}
cur = cur->son[t];
}
//找到完整的返回2,前缀字符串返回1
return cur->end ? 2 : 1;
}
public:
//插入字符串
void insert(string word) {
Node* cur=root;
for(char c:word){
c-='a';
if(cur->son[c]==nullptr){
cur->son[c]=new Node();
}
cur=cur->son[c];
}
cur->end=true;
}
//查询完整字符串
bool search(string word) {
return find(word)==2;
}
//查询前缀字符串
bool startsWith(string prefix) {
return find(prefix)!=0;
}
};
例题
例题1
爱找事的小Z
题目描述:
小 Z 同学非常喜欢找事,现在有很多名为“事”的字符串,现在小 Z 想要找“事”,请你帮助他判断,他今天是否找了两件相同的事。
输入描述
输入第一行包含一个整数 n,表示小 Z 今天找了多少事。
接下来 n行分别表示 n 件事。
事的输入量不超过 1e3,每个"事"字符串的长度不超过 1000,且所有字母均为小写。
输出描述
若有相同的事输出1,否则输出0。
输入输出样例
示例1
输入
12
acd
acd
asdfsdf
asd
f
saf
asdf
sfasdfs
f
asdf
asf
asdfs
输出
1
示例2
输入
21
adfasdfaasdf
asdfsfas
fa
sdfasd
fsd
fs
a
sda
fsd
afasd
f
sda
f
asdf
as
df
sda
gggggas
dfdsfe
def
a
输出
1
示例
输入
9
asdfasdfa
asdfb
asdc
fasd
fde
sadf
fg
hsadfasdfasdg
iggsadffsa
输出
0
#include<bits/stdc++.h>
using namespace std;
//节点
struct Node {
//每个节点有26个子节点
Node* son[26]{};
//末尾标记,可用于区分前缀字符串和完整字符串
bool end = false;
};
class Tire {
Node* root = new Node();
int find(string s) {
Node* cur = root;
for (char c : s) {
int t = c - 'a';
if (cur->son[t] == nullptr) {
//没找到返回0
return 0;
}
cur = cur->son[t];
}
//找到完整的返回2,前缀字符串返回1
return cur->end ? 2 : 1;
}
public:
//插入字符串
void intsert(string s) {
Node* cur = root;
for (char c : s) {
int t = c - 'a';
if (cur->son[t] == nullptr) {
cur->son[t] = new Node();
}
cur = cur->son[t];
}
cur->end = true;
}
//查询字符串
bool check(string s) {
return find(s) == 2;
}
};
int main() {
Tire tire;
int n;
cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++) {
string temp;
cin >> temp;
if (!tire.check(temp)) {
tire.intsert(temp);
}
else {
ans++;
}
}
if (ans) {
cout << 1;
}
else {
cout << 0;
}
return 0;
}
例题2
小蓝的神秘图书馆
问题描述
小蓝是图书馆的管理员,他负责管理图书馆的所有书籍。图书馆有 N本书,每本书都有名字,分别为 S1,S2,…,SN。
图书馆的读者们经常来询问小蓝,他们会给小蓝一个字符串 T,希望小蓝能告诉他们,图书馆里有多少本书的名字是以 T的前缀开头的。小蓝需要回答他们 M次这样的询问。
现在,小蓝需要你的帮助。你能帮助小蓝解决这个问题,从而提升图书馆的服务质量吗?
输入格式
第一行输入两个整数 N 和 M(1≤N,M≤1e4)。
接下来 N 行,每行输入一个字符串 Si,表示图书馆中的一本书的名字。
接下来 M行,每行一个字符串 T,表示读者的询问。
输入字符串的总长度不超过 2×1e5,且仅包含小写字母。
输出格式
对于每个询问,输出一个整数,表示图书馆中以字符串 T开头的书的数量。
每个答案占一行。
样例输入
5 2
ababc
ababd
aba
ab
a
abab
ccc
样例输出
2
0
#include<bits/stdc++.h>
using namespace std;
struct Node {
Node* son[26];
int end = 0;
};
class Tire {
Node* root = new Node();
int dfs(Node* cur) {
int res = 0;
if (cur == nullptr) {
return res;
}
if (cur->end) {
res += cur->end;
}
for (int i = 0; i < 26; i++) {
if (cur->son[i]) {
res+=dfs(cur->son[i]);
}
}
return res;
}
public:
void insert(string s) {
Node* cur = root;
for (char c : s) {
c = c - 'a';
if (cur->son[c] == nullptr) {
cur->son[c] = new Node();
}
cur = cur->son[c];
}
cur->end++;
}
int find(string s) {
Node* cur = root;
for (char c : s) {
c = c - 'a';
if (cur->son[c] == nullptr) {
return 0;
}
cur = cur->son[c];
}
return dfs(cur);
}
};
int main() {
Tire tire;
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
tire.insert(s);
}
for (int i = 1; i <= m; i++) {
string s;
cin >> s;
cout << tire.find(s) << '\n';
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 10;
string str[N],s[N];
ll n = 0, m = 0;
ll trie[N][27], cnt[N];
ll idx = 2;
void insert(string a){
ll p = 1;
ll k = a.length() - 1;
for (ll i = 1; i <= k; i++)
{
if (!trie[p][a[i] - '0']) trie[p][a[i] - '0'] = idx++;
p = trie[p][a[i] - '0'];
cnt[p]++;
}
}
ll query(string a){
ll p = 1;
ll k = a.length() - 1;
ll ans = 0x3f3f3f;
for (ll i = 1; i <= k; i++)
{
p = trie[p][a[i] - '0'];
ans = min(ans, cnt[p]);
}
return ans;
}
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (ll i = 1; i <= n; i++)
{
cin >> str[i];
str[i] = '0' + str[i];
insert(str[i]);
}
for (ll i = 1; i <= m; i++)
{
cin >> s[i];
s[i] = '0' + s[i];
cout << query(s[i]) << endl;
}
return 0;
}
01Tire
理论
x >> i & 1
(010)>>30 → (000)&(001) → 0
……
(010)>>2 → (000)&(001) → 0
(010)>>1 → (001)&(001) → 1
(010)>>0 → (010)&(001) → 0
1 << i
(001)<<2 → (100) → 4
(001)<<1 → (010) → 2
(001)<<0 → (001) → 1
res |= (1<< i)
(010)^(101) → (111) → 4+2+1=7
(011)^(101) → (110) → 4+2+0=6
(101)^(010) → (111) → 4+2+1=7
(111)^(010) → (101) → 4+0+1=5
模板
const int N = 1e5 + 10;
int son[32 * N][2], tot = 1;
//插入
void insert(int x)
{
int o = 1;
for (int i = 30; i >= 0; --i)
{
int y = x >> i & 1;
if (!son[o][y])
son[o][y] = ++tot;
o = son[o][y];
}
}
//查询
int query(int x)
{
int o = 1, res = 0;
for (int i = 30; i >= 0; --i)
{
int y = x >> i & 1;
//找相反方向的,0找1,1找0,异或后为1,大小最大
if (son[o][!y])
o = son[o][!y], res |= (1ll << i);
//如果该方向没有,则按原有方向走
else
o = son[o][y];
}
return res;
}
例题
XOR最大值
问题描述
给定一个整数数组 arr 和一个整数 q 表示查询的数量。接下来的 q行,每行给出一个整数 x。对于每个 x,你需要找到 arr 中的一个数 y使得 xXORy的值最大,然后输出这个最大值。
输入格式
第一行包含一个整数 n,表示数组 arr 的大小。
第二行包含 n个整数,分别是 arr的元素。(0≤arr[i]≤1e9)。
第三行包含一个整数 q,表示查询的数量。
接下来的 q行,每行包含一个整数 x。(0≤x≤1e9)。
输出格式
对于每个查询输出一行,表示 xXORy 的最大值。
样例输入
5
3 5 7 10 12
3
3
7
10
样例输出
15
13
15
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int son[32 * N][2], tot = 1;
void insert(int x)
{
int o = 1;
for (int i = 30; i >= 0; --i)
{
int y = x >> i & 1;
if (!son[o][y])
son[o][y] = ++tot;
o = son[o][y];
}
}
int query(int x)
{
int o = 1, res = 0;
for (int i = 30; i >= 0; --i)
{
int y = x >> i & 1;
if (son[o][!y])
o = son[o][!y], res |= (1ll << i);
else
o = son[o][y];
}
return res;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
int x;
cin >> x;
insert(x);
}
int q;
cin >> q;
while (q--)
{
int x;
cin >> x;
cout << query(x) << "\n";
}
}
#include<bits/stdc++.h>
using namespace std;
struct Node {
Node* son[2]{};
};
class Tire {
Node* root = new Node();
public:
void insert(int x) {
Node* cur = root;
for (int i = 30; i >= 0; i--) {
int y = x >> i & 1;
if (!cur->son[y]) {
cur->son[y] = new Node();
}
cur = cur->son[y];
}
}
int query(int x) {
Node* cur = root;
int res = 0;
for (int i = 30; i >= 0; i--) {
int y = x >> i & 1;
if (cur->son[!y]) {
res = res + (1 << i);
cur = cur->son[!y];
}
else {
cur = cur->son[y];
}
}
return res;
}
};
int main() {
Tire tire;
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
int x;
cin >> x;
tire.insert(x);
}
int q;
cin >> q;
while (q--)
{
int x;
cin >> x;
cout << tire.query(x) << "\n";
}
return 0;
}