字典树常见的题型和操作类型
字典树 Trie —— 常用操作笔记
节点:son[26],end标记单词结尾,cnt经过次数
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int tr[N][26];
int cnt[N]; //经过该节点的串数量
int ed[N]; //以这个节点结尾的单词数量
int idx=0;
1. 插入 insert
作用:把字符串放进字典树,沿途 cnt++,末尾 ed++
void ins(string s){
int u=0;
for(auto c:s){
int v=c-'a';
if(!tr[u][v]) tr[u][v]=++idx;
u=tr[u][v];
cnt[u]++;
}
ed[u]++;
}
2. 查询完整单词是否存在
作用:问这个字符串有没有作为一个完整单词插入过,返回个数
int qry_word(string s){
int u=0;
for(auto c:s){
int v=c-'a';
if(!tr[u][v]) return 0;
u=tr[u][v];
}
return ed[u];
}
3. 查询有多少串以 s 为前缀(P8306 核心)
作用:统计前缀数量,返回 cnt
int qry_prefix(string s){
int u=0;
for(auto c:s){
int v=c-'a';
if(!tr[u][v]) return 0;
u=tr[u][v];
}
return cnt[u];
}
4. 删除一个单词(简单版,有坑,谨慎用)
注意:只适合单词互不包含的场景;复杂情况要递归回溯
void del(string s){
int u=0;
for(auto c:s){
int v=c-'a';
u=tr[u][v];
cnt[u]--;
}
ed[u]--;
}
5. 多组数据清空(两种写法)
写法 A:记录用过的节点,只清这些(推荐,不 TLE)
vector<int>use;
//插入的时候新建节点就push_back(idx)
for(auto x:use){
memset(tr[x],0,sizeof tr[x]);
cnt[x]=0;ed[x]=0;
}
use.clear();
idx=0;
写法 B:时间戳标记(适合需要临时标记,不重置整棵树,CF858D 用的)
int tim[N],now;
now++;
if(tim[u]!=now){
tim[u]=now;
}
6. 数字字典树(0~9,你之前写的)
只改一行:int v=c-'0'; son[10]
int tr[N][10];
快速区分四个操作
- ins:建树,计数 + 1
- qry_prefix:走到末尾返回 cnt → 前缀数量
- qry_word:走到末尾返回 ed → 完整单词数量
- del:沿途 cnt--,末尾 ed--
易错点
- 根是 0,不要把 0 当成有效节点
cnt和ed含义分开:cnt = 经过,ed = 结尾- 删除很容易写炸,比赛尽量少写删除,能重建树就重建
- 字符集:小写 26,数字 10,大写再加
浙公网安备 33010602011771号