hihocoder第218周:AC自动机
问题描述
给定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();
}
}
hihocoder第218周:AC自动机的更多相关文章
- hihoCoder 1036 Trie图 AC自动机
题意:给定n个模式串和一个文本串,判断文本中是否存在模式串. 思路:套模板即可. AC代码 #include <cstdio> #include <cmath> #includ ...
- AC自动机学习
今天包括这一周开始学习AC自动机了,有点晚,但我感觉努努力还来得及.4月份还得认认真真攻图论,加油! 为2个月后的邀请赛及省赛.东北赛做准备. 推荐AC自动机学习地址:http://www.cppbl ...
- 【AC自动机&&Trie图】积累
以前KMP和后缀系列(主要是后缀数组,后缀自动机),都刷了一定数量的题,但是对于AC自动机,却有些冷落,罪过. 但是我感觉,在蓝桥杯比赛中AC自动机出现的概率比后缀系列大,简单的会考匹配,稍难一点会考 ...
- 一个在开源中国博客上讲解的AC自动机
原文出处:http://my.oschina.net/amince/blog/196426 原 荐 AC(Aho—Corasiek) 多模式匹配算法 摘要 如何在一篇文章中,搜索多个关键字,如何快速查 ...
- AC自动机练习题1:地图匹配
AC自动机板子,学习之前要是忘记了就看一下 1465: [AC自动机]地图匹配 poj1204 时间限制: 1 Sec 内存限制: 256 MB提交: 78 解决: 46[提交] [状态] [讨论 ...
- 小菜鸟 菜谈 KMP->字典树->AC自动机->trie 图 (改进与不改进)
本文的主要宗旨是总结自己看了大佬们对AC自动机和trie 图 的一些理解与看法.(前沿:本人水平有限,总结有误,希望大佬们可以指出) KMP分割线--------------------------- ...
- 洛谷-P5357-【模板】AC自动机(二次加强版)
题目传送门 -------------------------------------- 过年在家无聊补一下这周做的几道AC自动机的模板题 sol:AC自动机,还是要解决跳fail边产生的重复访问,但 ...
- 洛谷-P3796-【模板】AC自动机(加强版)
题目传送门 -------------------------------------- 过年在家无聊补一下这周做的几道AC自动机的模板题 sol:AC自动机,在fail边的基础上再加一个last边, ...
- 基于trie树做一个ac自动机
基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...
随机推荐
- iOS开发-邮件发送
Web开发的时候邮箱注册登录是必不可少的,手机号可以更换,不过相对而言,邮箱只是用于比较重要的时候用到,比如找工作的时候必填的邮箱,注册网站会员的邮箱验证.现在的手机和Web的其实操作是一样的,大多数 ...
- Intent 常用场景 FileProvider 拍照 裁剪 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- 配置nginx到后端服务器负载均衡
nginx和haproxy一样也可以做前端请求分发实现负载均衡效果,比如一个tomcat服务如果并发过高会导致处理很慢,新来的请求就会排队,到一定程度时请求就可能会返回错误或者拒绝服务,所以通过负载均 ...
- 解剖android中的闹钟app 一
首先,看一看android市场上有哪些主流的闹钟app了,我们来进行一个简单的评测: 一.正点闹钟 这是一款源自金山技术的闹钟app,其主力创始团队都是来自于金山,其装机量,下载量都是排名第一.老样子 ...
- 论文列表——text classification
https://blog.csdn.net/BitCs_zt/article/details/82938086 列出自己阅读的text classification论文的列表,以后有时间再整理相应的笔 ...
- 小议IE10下的DrawToBitmap方法
在完成博文“PS网页设计教程XXIV——从头设计一个漂亮的网站”后. 出于习惯,打开之前“利用Webbrowser类实现超长网页的截屏的实现(解决报错不能截取的难题)”中的代码的程序,截取博文作为资料 ...
- Android Studio 的 10 个你非常有可能不知道的技巧
本文首发:http://prototypez.github.io/2016/04/19/about-10-things-you-probably-didn-t-know-you-could-do-in ...
- 解剖 CPU
http://www.ruanyifeng.com/blog/2010/11/cpu_autopsy.html 有一个瑞典 Lund 大学物理学博士生,就真的这么干了,还把照片放到网上.我们知道,CP ...
- Hbase master启动报错:Failed construction of Master: class org.apache.hadoop.hbase.master.HMaster Caused by: java.net.UnknownHostException:
Hbase master启动报错: java.lang.RuntimeException: Failed construction of Master: class org.apache.hadoop ...
- 关于testNG和JUnit的对比
关于选择JUnit还是选testNG,这几篇文章,建议读一读: API参考文档: Junit API文档:http://junit.org/junit4/javadoc/latest/index.ht ...