trie树分为普通的trie树和01trie树
两者可以实现成树,很大一部分原因是:只有26个字母和01两种状态,一个结点度数不会太大,而且字符串长度和位数不会很大,更容易存储
普通trie树维护了一堆字符串集合的前缀,insert和query复杂度都是树的深度,最深是字符串长度,因此字符串长度在不是很长的情况下可以用trie树存
01trie树维护了一堆数集合和位表示前缀,insert和query复杂度也是树的深度,最深才二三十(log)
当我们要看一个字符串集合内某个字符串的出现次数时可以用普通trie
当我们看一个数的集合内二进制表示满足某个条件的数的个数可以用01trie
trie树做题步骤:
普通trie树:
去确定好要维护哪个字符串集合(去哪些字符串里找满足条件的字符串)
造trie树
01trie树:
去确定好要维护哪个数字集合(去哪些数里找二进制满足某些条件的数)
造trie树
来看一些题:
Trie字符串统计
835. Trie字符串统计 - AcWing题库
trie树裸题,直接打板子即可
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e5+10;
string op,s;
int n,idx=0;
int son[mxn][26],cnt[mxn];
void insert(string s){
int p=0;
for(int i=0;i<s.size();i++){
int u=s[i]-'a';
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;
}
int query(string s){
int p=0;
for(int i=0;i<s.size();i++){
int u=s[i]-'a';
if(!son[p][u]) return 0;
p=son[p][u];
}
return cnt[p];
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>op>>s;
if(op=="I") insert(s);
else cout<<query(s)<<'\n';
}
}
最大异或对
AcWing 143. 最大异或对 - AcWing
01trie裸题
我们去按位贪心即可,从高位向低位贪心
如果该位能变成1,就变成1,如果不行就只能将就一下(像极了爱情
那么对于第i位我们只需维护第i位前缀的trie树就行,去已经出现过的数的集合里找二进制满足一定条件的数
去遍历trie树,如果该位是1,就期待它为0,否则就期待它为1
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e5+10;
int n,tot=0;
int a[mxn],son[mxn*32][2];
void insert(int x){
int p=0;
for(int i=30;i>=0;i--){
int t=(x>>i)&1;
if(!son[p][t]) son[p][t]=++tot;
p=son[p][t];
}
}
int query(int x){
int p=0,res=0;
for(int i=30;i>=0;i--){
int t=(x>>i)&1;
if(son[p][t^1]){
p=son[p][t^1];
res|=(1<<i);
}
else p=son[p][t];
}
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
insert(a[i]);
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,query(a[i]));
}
cout<<ans<<'\n';
}
[TJOI2010] 阅读理解
P3879 [TJOI2010] 阅读理解 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
本来这题我想直接造N课trie树然后查询,然后发现这样会炸内存
其实只需要按顺序造一棵trie树就行,然后在造的时候记录第i篇短文的所有字符的出现次数(是否出现)
设cnt[p][id]表示第id篇短文第p个位置的字符是否出现
n最多取1e3,每篇短文最多有5e3个字符,因此第一维该设5e6,第二维1e3,这样也会爆内存
但是注意到它的值只会取0和1,因此可以bitset优化
在压位BFS中,也可以用bitset优化时间复杂度和空间复杂度
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e3+10,mxv=5e6+10;
bitset<1001> cnt[500007];
char s[mxn];
int n,m,L,idx=0;
int son[300007][26];
inline void insert(char *s,int id){
int p=0;
for(int i=0;s[i];i++){
int u=s[i]-'a';
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
cnt[p][id]=1;
}
inline void query(char *s){
int p=0;
for(int i=0;s[i];i++){
int u=s[i]-'a';
if(!son[p][u]){
cout<<' '<<'\n';
return;
}
p=son[p][u];
}
for(int i=1;i<=n;i++){
if(cnt[p][i]==1) cout<<i<<' ';
}
cout<<'\n';
}
int main(){
//ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>L;
for(int j=1;j<=L;j++){
cin>>s;
insert(s,i);
}
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>s;
query(s);
}
return 0;
}
最长异或路径
P4551 最长异或路径 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:
首先它说树上任意两结点的异或距离,这很让人想起树上前缀和
事实上,u和v的异或距离就是sum[u]^sum[v],其中sum数组是该结点到根的前缀异或(因为两个相同的数异或是0)
因此我们只需dfs预处理出树上前缀和,然后以所有结点的sum值作为集合去造trie树,然后查询两个数在集合里的异或最大值就行
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e5+10,mxe=1e5+10;
struct ty{
int to,next,w;
}edge[mxe<<1];
int n,u,v,w,tot=0,idx=0;
int head[mxn],sum[mxn],son[mxn*32][2];
void add(int u,int v,int w){
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void init(){
tot=0;
for(int i=0;i<=n;i++) head[i]=-1;
}
void dfs(int u,int fa){
for(int i=head[u];~i;i=edge[i].next){
if(edge[i].to==fa) continue;
sum[edge[i].to]=sum[u]^edge[i].w;
dfs(edge[i].to,u);
}
}
void insert(int x){
int p=0;
for(int i=30;i>=0;i--){
int u=(x>>i)&1;
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
}
int query(int x){
int p=0,res=0;
for(int i=30;i>=0;i--){
int u=(x>>i)&1;
if(son[p][u^1]) p=son[p][u^1],res|=(1<<i);
else p=son[p][u];
}
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
init();
for(int i=1;i<=n-1;i++){
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(1,-1);
for(int i=1;i<=n;i++) insert(sum[i]);
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,query(sum[i]));
cout<<ans<<'\n';
}