字典树常见的题型和操作类型

字典树 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--

易错点

  1. 根是 0,不要把 0 当成有效节点
  2. cnt 和 ed 含义分开:cnt = 经过,ed = 结尾
  3. 删除很容易写炸,比赛尽量少写删除,能重建树就重建
  4. 字符集:小写 26,数字 10,大写再加
posted @ 2026-10-20 18:30  事事随风散  阅读(2)  评论(0)    收藏  举报