【模板】字典树 / Trie(前缀统计)

前缀统计(Trie 树模板题)

一、题目描述

给定 n 个模式串 s1, s2, ..., sn 和 q 次询问。

每次询问给定一个文本串 ti,回答:这 n 个字符串中,有多少个字符串以 ti 为前缀?

对应题目:洛谷P8306 【模板】字典树 / Trie

二、思路分析

Trie 树做法

把所有模式串插入一棵 Trie 树。Trie 的特点就是公共前缀共用同一条路径。

插入时,在路径上的每个节点都记录一个计数器 num:

  • num 表示有多少个模式串经过这个节点
  • 即有多少个模式串以「从根到该节点的路径」为前缀

查询时,从根沿 ti 逐字符向下走:

  • 如果能走完整个 ti,到达节点 u,那么 trie[u].num 就是答案
  • 如果中途某个字符对应的子节点不存在,直接返回 0

三、字符映射

题目中字符串大小写敏感,且包含:

  • 小写字母 a ~ z:26 个
  • 大写字母 A ~ Z:26 个
  • 数字 0 ~ 9:10 个

共 62 个字符,需要映射到 0 ~ 61:

int getpos(char c)
{
    if (c <= 'z' && c >= 'a') return c - 'a';        // 0 ~ 25
    else if (c <= 'Z' && c >= 'A') return c - 'A' + 26; // 26 ~ 51
    else return c - '0' + 52;                         // 52 ~ 61
}

ch 数组开到 65 足够。

四、AC 代码

#include<bits/stdc++.h>
using namespace std;

struct Node
{
    int ch[65];
    int num;
    Node()
    {
        memset(ch, -1, sizeof(ch));
        num = 0;
    }
};

vector<Node> trie;

int createNode()
{
    trie.emplace_back(Node());
    return trie.size() - 1;
}

int getpos(char c)
{
    if (c <= 'z' && c >= 'a') return c - 'a';
    else if (c <= 'Z' && c >= 'A') return c - 'A' + 26;
    else return c - '0' + 52;
}

void insert(string s)
{
    int u = 0;
    trie[u].num++;
    for (int i = 0; i < s.size(); i++)
    {
        int pos = getpos(s[i]);
        if (trie[u].ch[pos] == -1)
            trie[u].ch[pos] = createNode();
        u = trie[u].ch[pos];
        trie[u].num++;
    }
}

int query(string s)
{
    int u = 0;
    for (int i = 0; i < s.size(); i++)
    {
        int pos = getpos(s[i]);
        if (trie[u].ch[pos] == -1) return 0;
        u = trie[u].ch[pos];
    }
    return trie[u].num;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n, q;
        cin >> n >> q;

        trie.clear();
        trie.emplace_back(Node());  // 根节点

        string s;
        for (int i = 0; i < n; i++)
        {
            cin >> s;
            insert(s);
        }

        for (int i = 0; i < q; i++)
        {
            cin >> s;
            cout << query(s) << endl;
        }
    }
    return 0;
}

五、代码说明

1. 节点定义

struct Node
{
    int ch[65];   // 62 个字符映射,开到 65 保险
    int num;      // 经过该节点的模式串数量
    Node()
    {
        memset(ch, -1, sizeof(ch));
        num = 0;
    }
};
  • ch[i] = -1 表示该子节点不存在
  • num 记录经过该节点的字符串数量

2. 插入时更新 num

根节点也要更新,因为空串也是所有字符串的前缀(如果询问是空串的话)。

trie[u].num++;   // 进入节点时更新

路径上每个节点都要 num++,因为每个节点都对应一个前缀。

3. 查询

沿 ti 走到底,返回 trie[u].num。

中途走不下去说明没有模式串以 ti 为前缀,返回 0。

4. 多组数据的处理

每次新数据前:

trie.clear();
trie.emplace_back(Node());

清空 trie 并重建根节点。

六、复杂度

操作 时间复杂度 说明
插入一个模式串 O(L) L 为字符串长度
查询一个前缀 O(L) 同上
总复杂度 O((n + q) × L) —

空间复杂度:O(总字符数 × 字符集大小)。

七、总结

要点 内容
数据结构 Trie 树
核心操作 插入时路径上每个节点 num++
查询 沿前缀走到底,返回末尾节点的 num
关键 每个节点代表一个前缀,num 记录经过它的字符串数
posted @ 2026-09-24 21:18  leaderling  阅读(7)  评论(0)    收藏  举报