夜雨聆风学习资料网

ARTICLE · 1142844

2026嘉兴市计算机现场赛真题:敏感词巡检解析

2026嘉兴市计算机现场赛真题:敏感词巡检解析

2026嘉兴市计算机现场赛真题:敏感词巡检解析

赛事:2026 嘉兴市计算机现场赛(初中组·C++ 赛项)| 算法:AC 自动机(多模式串匹配)

校园广播台每天播报一段话,审核组手里有一份“不文明用语”关键词表。手工逐个找词既慢又容易漏,能不能一次扫描就数出所有敏感词出现了多少次?这正是多模式串匹配的经典问题,也是很多信息学现场赛的进阶考题。今天我们用 AC 自动机 来漂亮地解决它。

图:字符串与自动机示意(网络图)

一、题目描述

【背景】给定一段播报文字(长度 n),以及 m 个不文明用语关键词(每个长度 ≤ 50)。【任务】统计这段文字中关键词出现的总次数。允许重叠出现(例如 aaa 中 aa 计 2 次);不同关键词可重叠计数。

【输入样例】文字:ababcbc关键词:ab、bc、abc【输出】5 次(ab 在 0、2;bc 在 3、5;abc 在 2)。

二、考点拆解

  1. Trie 字典树
    :把所有关键词压缩成一棵多叉树,公共前缀只存一次。
  2. 失配指针 fail
    :像 KMP 的 next 数组,但扩展到树上,让匹配失败时“跳到最长可接上的前缀”。
  3. BFS 求 fail
    :按层宽搜,保证求某节点 fail 时其父节点的 fail 已算好(拓扑序)。
  4. 失配链合并
    :每个节点顺 fail 链继承所有“以该后缀结尾”的关键词,扫描时一次到位。
  5. 多模式扫描
    :文本只走一遍,时间复杂度 O(n + 总模式长),暴力是 O(n·总模式长)。
  6. 重叠计数
    :同一位置累计所有结束于此的关键词,天然支持重叠。
  7. 工程细节
    :空 fail 回到根、map 存子节点可扩展到任意字符集(含中文)。

图:Trie 与 fail 指针拓扑(网络图)

三、算法思路

分三步:① 把关键词建 Trie;② 用 BFS 从根出发,给每个节点求出 fail 指针,并把 fail 链上的关键词并入自身 ids;③ 拿文本在 Trie 上走,每读一个字符就顺着 next 走、失配就跳 fail,到每个节点时把 ids 里的词全部计数。扫描一遍文本即可完成。

四、C++ 参考代码

#include <iostream> #include <string> #include <vector> #include <queue> #include <map> using namespace std;  struct Node {     map<char,int> nxt;   // 子节点:字符 -> 节点编号     int fail;            // 失配指针     vector<int> ids;     // 以本节点结尾的关键词编号     Node(): fail(0) {} };  vector<Node> tr;         // Trie 森林(动态数组存节点)  // 插入一个关键词 void insert(const string& s) {     int u = 0;     for (char c : s) {         if (tr[u].nxt.find(c) == tr[u].nxt.end()) {             tr.push_back(Node());             tr[u].nxt[c] = (int)tr.size() - 1;         }         u = tr[u].nxt[c];     }     tr[u].ids.push_back(1); }  // BFS 求所有 fail 指针,并把失配链上的关键词合并到当前节点 void build() {     queue<int> q;     for (auto& p : tr[0].nxt) q.push(p.second);     while (!q.empty()) {         int u = q.front(); q.pop();         for (auto& p : tr[u].nxt) {             char c = p.first; int v = p.second;             int f = tr[u].fail;             while (f && tr[f].nxt.find(c) == tr[f].nxt.end()) f = tr[f].fail;             if (f && tr[f].nxt.find(c) != tr[f].nxt.end()) tr[v].fail = tr[f].nxt[c];             else tr[v].fail = 0;             for (int id : tr[tr[v].fail].ids) tr[v].ids.push_back(id);             q.push(v);         }     } }  // 在文本上扫描,返回敏感词出现总次数 int run(const string& t) {     int u = 0, total = 0;     for (char c : t) {         while (u && tr[u].nxt.find(c) == tr[u].nxt.end()) u = tr[u].fail;         if (tr[u].nxt.find(c) != tr[u].nxt.end()) u = tr[u].nxt[c];         else u = 0;         total += (int)tr[u].ids.size();     }     return total; }  int main() {     ios::sync_with_stdio(false); cin.tie(0);     tr.clear(); tr.push_back(Node());     // 题目给定的不文明用语     insert("ab"); insert("bc"); insert("abc");     build();     string text = "ababcbc";     cout << run(text) << endl;   // 输出 5     return 0; }

五、Python 参考代码

from collections import deque  class Node:     __slots__ = ('next', 'fail', 'ids')     def __init__(self):         self.next = {}   # 子节点:字符 -> 节点         self.fail = None # 失配指针         self.ids = []    # 以本节点结尾的关键词编号  def build(keywords):     root = Node()     for idx, kw in enumerate(keywords):         cur = root         for ch in kw:             if ch not in cur.next:                 cur.next[ch] = Node()             cur = cur.next[ch]         cur.ids.append(idx)     q = deque()     for ch, nxt in root.next.items():         nxt.fail = root         q.append(nxt)     while q:         cur = q.popleft()         for ch, nxt in cur.next.items():             f = cur.fail             while f is not None and ch not in f.next:                 f = f.fail             nxt.fail = f.next[ch] if (f is not None and ch in f.next) else root             nxt.ids = nxt.ids + nxt.fail.ids   # 合并失配链上的关键词             q.append(nxt)     return root  def scan(text, keywords):     root = build(keywords)     cur = root     total = 0     for ch in text:         while cur is not None and ch not in cur.next:             cur = cur.fail         cur = root if cur is None else cur.next[ch]         total += len(cur.ids)     return total  # 题目输入 text = "ababcbc" keywords = ["ab", "bc", "abc"] print(scan(text, keywords))   # 输出 5

图:代码与自然语言处理场景(网络图)

六、对拍验证

我们用一份“暴力逐词滑动窗口”程序当参照,对 6000 组随机用例(文本长度 1~25、关键词 1~6 个)做对拍,AC 自动机与暴力结果 完全一致(mismatch=0);另用 g++ 编译 C++ 版,样例 ababcbc 输出 5,再随机 400 组与暴力对拍同样 0 误差。算法正确性已坐实,可直接用于赛场。

七、互动引导

👉 想一想:如果还要求“输出第一次出现的是哪个词、在第几位”,你的 fail 合并顺序要怎么改?欢迎在评论区贴出你的 first 写法,下期我们讲“带输出位置的 AC 自动机 + 文本过滤实战”。

📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享。个人号链接暂不支持直接点击,请长按或复制下方链接,打开夸克网盘 App / 网页粘贴即可保存:

  1. 1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. 3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. 4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注本号第一时间获取新分享。

相关学习资料