题目链接

问题描述

给定n个单词,给定一个长字符串s,单词总长度和字符串s的长度都不超过1e5。要求把s中所有的出现单词的位置用*替代。
例如:
样例输入

2
abc
cd
abcxyzabcd

样例输出

***xyz****

关键一点在于:先找到应该打*的全部字符,然后再统一改写成*,也就是要考虑abcd同时命中abc和cd的情况。

思路

AC自动机是算法世界中最美妙的事物之一。它像一个大合唱一样,过去的KMP、字典树、树形DP、有限状态自动机一股脑地来了,聚合在一起,最终完美地达到了O(N)的时间复杂度。

像KMP一样,关键在于求fail指针。AC自动机相比字典树什么也没多,仅仅多了一堆fail指针,让结点和结点之间的联系变得紧密,神奇恰恰发生在一对乱指的指针上。

算法就是玩指针,有时候一根指针,有时候多根指针;有时候从前往后走,有时候从后往前走,有时候转圈走;有时候一步一步走,有时候一片一片走。

求fail指针的过程跟KMP算法非常类似,只不过一变多。最关键的是一开始的时候要假设已经fail了,把root结点的儿子们的fail初始化为root,然后就可以往后走了。

此题一个比较隐蔽的case:

2
abcde
ab
abcdxabcdm

应该输出**cdx**cdm,注意初始化fail的时候要把该结点是否为terminal考虑进去,并对结点是否为terminal进行改写。

代码


import java.util.*;public class Main {
//字典树结点
class TrieNode {char ch;//此值仅用于调试TrieNode[] sons;TrieNode fail;char[] value;//如果是terminal,则nodeValue不为空boolean isTerminal() {return value != null;}TrieNode(char ch) {this.ch = ch;}
}//命中:pos表示命中的最后位置,s表示命中的单词
class Hit {int beg;char[] s;Hit(char[] s, int beg) {this.s = s;this.beg = beg;}
}class Trie {TrieNode root = new TrieNode(' ');Trie(char[][] patterns) {for (char[] i : patterns) insert(i);build();}void insert(char[] s) {TrieNode now = root;for (char c : s) {//遇山开路,遇水铺桥if (now.sons == null) {now.sons = new TrieNode[26];}if (now.sons[c - 'a'] == null) {now.sons[c - 'a'] = new TrieNode(c);}now = now.sons[c - 'a'];}now.value = s;}List<Hit> query(char[] s) {List<Hit> hits = new ArrayList<>(maxn);TrieNode now = null;for (int i = 0; i < s.length; i++) {char c = s[i];if (now == null) now = root;while (now != root) {if (now.sons == null || now.sons[c - 'a'] == null) {now = now.fail;continue;}break;}now = now.sons[c - 'a'];if (now == null) {now = root;continue;}if (now.isTerminal()) hits.add(new Hit(now.value, i - now.value.length + 1));}return hits;}TrieNode getFail(TrieNode pre, int ch) {while (pre != root) {if (pre.sons != null && pre.sons[ch] != null)break;pre = pre.fail;}if (pre.sons[ch] != null) return pre.sons[ch];return root;}void build() {Queue<TrieNode> q = new LinkedList<>();root.fail = root;//初始化第一层,假设一开始没命中,之后应该怎么办/*** 某种程度上,AC自动机相当于动态规划* */for (TrieNode i : root.sons) {if (i != null) {q.add(i);i.fail = root;}}while (!q.isEmpty()) {TrieNode now = q.poll();if (now.sons == null) continue;for (int i = 0; i < now.sons.length; i++) {if (now.sons[i] == null) continue;now.sons[i].fail = getFail(now.fail, i);//如果我不是终点,我需要把自己设置成终点/*** 此处非常关键* */if (now.sons[i].fail.isTerminal() && !now.sons[i].isTerminal()) {now.sons[i].value = now.sons[i].fail.value;}q.add(now.sons[i]);}}//        show(root);}
}class Node {int x, type;Node(int x, int type) {this.x = x;this.type = type;}
}final int maxn = 1007;
int[] lens;Main() {Scanner cin = new Scanner(System.in);int n = cin.nextInt();lens = new int[n];char[][] patterns = new char[n][];for (int i = 0; i < n; i++) {patterns[i] = cin.next().toCharArray();}Trie tree = new Trie(patterns);char[] s = cin.next().toCharArray();List<Hit> hits = tree.query(s);List<Node> nodes = new ArrayList<>(2 * hits.size());for (Hit i : hits) {nodes.add(new Node(i.beg, 1));nodes.add(new Node(i.beg + i.s.length, -1));}nodes.sort(Comparator.comparing(x -> x.x));StringBuilder builder = new StringBuilder();int in = 0;int j = 0;for (int i = 0; i < s.length; i++) {while (j < nodes.size() && nodes.get(j).x <= i) {in += nodes.get(j).type;j++;}if (in == 0) builder.append(s[i]);else builder.append('*');}System.out.println(builder.toString());
}public static void main(String[] args) {new Main();
}
}

转载于:https://www.cnblogs.com/weiyinfu/p/9605860.html

hihocoder第218周:AC自动机相关推荐

  1. HiHocoder 1036 : Trie图 AC自动机

    Trie图 先看一个问题:给一个很长很长的母串 长度为n,然后给m个小的模式串.求这m个模式串里边有多少个是母串的字串. 最先想到的是暴力O(n*m*len(m)) len(m)表示这m个模式串的平均 ...

  2. HihoCoder - 1877 Approximate Matching(AC自动机+dp)

    题目链接:点击查看 题目大意:给出一个长度为 n 的 01 字符串,规定近似相等的定义是,两个长度相同的字符串,至多只有一个位置不相同,问长度为 m 的 01 串中,有多少个字符串,存在一个长度为 n ...

  3. HDU 2243考研路茫茫——单词情结 (AC自动机+矩阵快速幂)

    背单词,始终是复习英语的重要环节.在荒废了3年大学生涯后,Lele也终于要开始背单词了. 一天,Lele在某本单词书上看到了一个根据词根来背单词的方法.比如"ab",放在单词前一般 ...

  4. Censored! POJ - 1625 AC自动机+大数DP

    题意: 给出一n种字符的字典,有p个禁用的单词, 问能组成多少个不同的长度为m的合法字符串.(m<=50) 题解: 是不是个我们之前做的题目非常非常像,题意都一样. 直接将上次写的AC自动机+矩 ...

  5. 算法导论—AC自动机

    华电北风吹 日期:2016-05-03 AC自动机是比较高效的多模式匹配算法.类似于KMP在模式串上的状态转移算法,AC自动机通过在trie树上建立状态转移,使得对匹配串遍历一遍就可以找到所有的模式串 ...

  6. 极限定律 My Algorithm Space AC自动机算法详解

    转载自:http://www.cppblog.com/mythit/archive/2009/04/21/80633.html 首先简要介绍一下AC自动机:Aho-Corasick automatio ...

  7. 敏感词屏蔽——AC自动机

    个人微信公众号:程序员宅急送 一.算法介绍 上一周我们讲了Trie树,这次的AC自动机是Trie树的一个改进版,也是一个多模式串匹配算法. 区别在于Trie树--在一堆模串中找寻符合条件的前缀(搜索引 ...

  8. Aho-Corasick 多模式匹配算法(AC自动机) 的算法详解及具体实现

    多模式匹配 多模式匹配就是有多个模式串P1,P2,P3-,Pm,求出所有这些模式串在连续文本T1-.n中的所有可能出现的位置. 例如:求出模式集合{"nihao","ha ...

  9. 【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组

    [BZOJ2434][NOI2011]阿狸的打字机 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P ...

最新文章

  1. SAP HUM 带HU的TO单对应的外向交货单VL09取消PGI之后不能对该交货单执行LT0G做WM层面的返架?
  2. eBay是如何进行大数据集元数据发现的
  3. Python函数内置函数
  4. MySQL导入csv文件内容到Table及数据库的自增主键设置
  5. left join 后边的on条件 小记
  6. Good Number Gym - 102769G 2020年CCPC秦皇岛分站赛
  7. 通过Java代码打开浏览器,本地文件目录以及ftp站点
  8. altiumer designer学习
  9. 在Shell中使用函数文件,引入文件
  10. JavaScript计算指定日期与当前日期的相差天数
  11. 米斯特白帽培训讲义 漏洞篇 SQL 注入
  12. php 修改多级菜单,用PHP实现多级树型菜单
  13. java 异步处理数据格式_spring mvc对异步请求的处理
  14. node-inspector调试工具使用方法
  15. AgileEAS.NET平台开发案例-药店系统-项目说明
  16. sublime text3 eslint 安装教程
  17. CNN 卷积神经网络 池化层Pooling 动手学深度学习v2 pytorch
  18. 公众号文章阅读量数据导出
  19. WordPress初学者入门教程-“经典”所见即所得编辑器
  20. 项目整体管理:项目整体管理概述

热门文章

  1. 投篮机投篮有技巧吗_卡梅伦·约翰逊:投篮高效,跑位积极,会是太阳队外线新答案吗?...
  2. DocumentHelper用法
  3. vue过滤器微信小程序过滤器和百度智能小程序过滤器
  4. 特征工程之归一化及标准化
  5. 51nod 1009 数字1的数量
  6. mysql查看线程详解(转载)
  7. 哦~最重要的产品链接忘了发了
  8. hdu 1710 Binary Tree Traversals (二叉树)
  9. hadoop错误总结
  10. UIView的layoutSubviews和drawRect