Trie树
思路
\(trie\),又称字典树,是一种树形结构,可以在 \(O(n)\) 的时间内查找字符串是否存在。\(Trie\) 树根节点为空,支持动态开点,每个点储存儿子的编号。\(Trie\) 树通常是存储字符串,但有时也可以存储别的信息。空间复杂度为 \(O(n\times len)\) 相对较大。不过 \(Trie\) 树还有一些别的变体,例如 \(01Trie\) 可以是常用于处理异或操作的一种数据结构。
例如有字符串 \(ABCAAA\) 与 \(ABCABA\) 与 \(BA\) 三个串,其 \(trie\) 是这样的。
\(Code\)
inline void Build(string s){ //构造Trie树
int l = s.length();
int now = 0; //Trie的指针
for(register int i = 0; i < l; ++i){
if(AC[now].vis[s[i]-'a'] == 0) //Trie树没有这个子节点
AC[now].vis[s[i]-'a'] = ++cnt; //构造出来
now = AC[now].vis[s[i]-'a']; //向下构造
}
AC[now].end += 1; //标记单词结尾
}
例题一:于是他错误的点名开始了
思路
\(trie\) 树版题,构建 \(trie\) 树后直接对 \(end\) 的值进行判定即可。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
string s;
int n, m;
int cnt;
struct Trie{
int vis[26], end;
}tree[500005];
template<typename T>
inline void read(T&x){
x = 0; char q; bool f = 1;
while(!isdigit(q = getchar())) if(q == '-') f = 0;
while(isdigit(q)){
x = (x<<1) + (x<<3) + (q^48);
q = getchar();
}
x = f?x:-x;
}
template<typename T>
inline void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9) write(x/10);
putchar(x%10^48);
}
inline void build(string s){
int len = s.size();
int u = 0;
for(register int i = 0; i < len; ++i){
if(!tree[u].vis[s[i]-'a']) tree[u].vis[s[i]-'a'] = ++cnt;
u = tree[u].vis[s[i]-'a'];
}
tree[u].end = 1;
}
inline short search(string s){
int len = s.size();
int u = 0;
for(register int i = 0; i < len; ++i){
if(!tree[u].vis[s[i]-'a']) return 0;
u = tree[u].vis[s[i]-'a'];
}
if(tree[u].end == 2) return 2;
tree[u].end = 2;
return 1;
}
int main(){
read(n);
for(register int i = 1; i <= n; ++i){
cin >> s;
build(s);
}
read(m);
for(register int i = 1; i <= m; ++i){
cin >> s;
short t = search(s);
if(t == 0) cout << "WRONG" << endl;
if(t == 1) cout << "OK" << endl;
if(t == 2) cout << "REPEAT" << endl;
}
return 0;
}
例题二:\(Secret\) \(Message\) \(G\)
思路
很典型的版题,直接对文本串建立 \(Trie\) 树,然后跑模式串即可,跑完会出现一下几种情况。
\(end\) 表示该节点有多少个文本串结束,\(sum\) 表示该节点有多少串经过(结束也算)。
- 模式串是文本串前缀,直接输出一路加的 \(end\) 值即可。
- 文本串是模式串前缀,之前的 \(end\) 值减最后一个节点的 \(end\) 值加最后一个节点的 \(sum\) 值即可,其实很容易理解,\(sum\) 表示了经过及结束的点,所以减去 \(end\) 再加 \(sum\) 即为经过了此处点不以此为结尾的串,也就是模式串的前缀。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int n, m;
struct Trie{
int son[2], sum, end; //sum为每个点的经过次数,end为每个点作为结尾的次数,son[i]表示i儿子编号
}tree[500005];
int a[10005], num, cnt;
inline void build(int x){ //建立trie树
int u = 0;
for(register int i = 1; i <= x; ++i){
if(!tree[u].son[a[i]]) tree[u].son[a[i]] = ++cnt;
u = tree[u].son[a[i]];
tree[u].sum++; //每个节点经过即+1
}
tree[u].end++; //终止点+1
}
inline void search(int x){ //查找字符串
int u = 0, ans = 0;
for(register int i = 1; i <= x; ++i){
if(!tree[u].son[a[i]]){ //如果已经未建立该点,说明在trie树中已经与他无交集了,则输出答案
cout << ans;
putchar('\n');
return;
}
u = tree[u].son[a[i]];
ans += tree[u].end; //ans加上以这个点结尾的答案
}
ans -= tree[u].end; //ans减去以这个点为结尾的数量
ans += tree[u].sum; //ans加上经过终止点的数量
cout << ans;
putchar('\n');
return;
}
int main(){
cin >> m >> n;
for(register int i = 1; i <= m; ++i){
cin >> num;
for(register int j = 1; j <= num; ++j) cin >> a[j];
build(num);
}
for(register int i = 1; i <= n; ++i){
cin >> num;
for(register int j = 1; j <= num; ++j) cin >> a[j];
search(num);
}
return 0;
}
例题三:最长异或路径
思路
看到为异或操作,首先想到 \(Trie\),可以发现这样一个性质:若用 \(ans_{u,v}\) 表示 \(u\) 到 \(v\) 的异或距离,因为相同两个数异或值为 \(0\),所以其值就是 \(ans_{u,\gcd_{u,b}}\oplus ans_{v,\gcd_{u,v}}\),因此,求二者的异或距离就可以根据二者到根的距离的异或值求解。
于是直接根据路径上的异或值构建一棵 \(01Trie\) 树,每个路径代表它到根的异或路径。通过贪心的思想,高位为 \(1\) 总是比高位为 \(0\) 大,所以如果异或值为 \(1\) 的儿子节点存在就走这个节点,如果不存在或者另一个儿子异或值为 \(0\) 就走原节点。
首先做一次 \(dfs\) 记录路径异或值,然后根据异或值建立一棵 \(Trie\) 树,最后在 \(Trie\) 树基础上进行贪心即可。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int n, x, y, z;
int head[100005], ne[200005], e[200005], val[200005], idx; //边的信息
int Xor[100005], cnt, maxn; //Xor[i]表示i点到根节点异或值
struct Trie{
int son[2]; //表示01儿子编号
}tree[3100005]; //操作数*31
inline void add(int u, int v, int num){
e[++idx] = v;
ne[idx] = head[u];
val[idx] = num;
head[u] = idx;
}
inline void dfs(int u, int fa){ //dfs记录每个点到根的异或值
for(register int i = head[u]; i; i = ne[i]){
int v = e[i];
if(v == fa) continue;
Xor[v] = Xor[u]^val[i];
dfs(v, u);
}
}
inline void build(int x, int u){ //建立trie树
for(register int i = 1<<30; i; i >>= 1){
bool f = x&i;
if(!tree[u].son[f]) tree[u].son[f] = ++cnt;
u = tree[u].son[f];
}
}
inline int query(int x, int u){ //查询最大值
int ans = 0;
for(register int i = 1<<30; i; i >>= 1){
bool f = x&i;
if(tree[u].son[f^1]) ans += i, u = tree[u].son[f^1]; //若有节点为1,则走这个节点
else u = tree[u].son[f]; //若没有,则继续当前的的节点
}
return ans;
}
int main(){
cin >> n;
for(register int i = 1; i < n; ++i){
cin >> x >> y >> z;
add(x, y, z);
add(y, x, z);
}
dfs(1, 0);
for(register int i = 1; i <= n; ++i) build(Xor[i], 0);
for(register int i = 1; i <= n; ++i) maxn = max(maxn, query(Xor[i], 0)); //对于每个点,都查询一次最大值
cout << maxn;
return 0;
}