这章对现在的我来说有点难,要是不写点东西,三天后怕是就一无所有了。

但写这个没有营养的blog的目的真的不是做题或提升,只是学习学习代码和理解一些概念。

现在对AC自动机的理解还十分浅薄,这里先贴上目前我看过的文章:

  1. 深入理解Aho-Corasick自动机算法
  2. AC 自动机学习笔记

AC自动机相比Trie多了失配边,结点到结点间的状态转移,结点到根的状态转移。

这里fail的定义是:使当前字符失配时跳转到另一段从root开始每一个字符都与当前已匹配字符段某一个后缀完全相同且长度最大的位置继续匹配。

A - Keywords Search

题意:给多个模式串,求文本串中有多少模式串是子串。

这里直接贴上kuangbin大佬的模板。

 #include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
struct Trie{
int next[][], fail[], end[];
int root, L;
int newnode(){
for(int i=; i<; i++){
next[L][i] = -;
}
end[L++] = ;
return L-;
}
void init(){
L = ;
root = newnode();
}
void insert(char buf[]){
int len = strlen(buf);
int now = root;
for(int i=; i<len; i++){
if(next[now][buf[i]-'a'] == -){
next[now][buf[i]-'a'] = newnode();
}
now = next[now][buf[i]-'a'];
}
end[now]++;
}
void build(){
queue<int> Q;
fail[root] = root;
for(int i=; i<; i++){
if(next[root][i] == -){
next[root][i] = root;
}else{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
}
while( !Q.empty() ){
int now = Q.front();
Q.pop();
for(int i=; i<; i++){
if(next[now][i] == -){
next[now][i] = next[fail[now]][i];
}else{
fail[next[now][i]] = next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
}
int query(char buf[]){
int len = strlen(buf);
int now = root;
int res = ;
for(int i=; i<len; i++){
now = next[now][buf[i]-'a'];
int temp = now;
while(temp != root){
res += end[temp];
end[temp] = ;
temp = fail[temp];
}
}
return res;
}
void debug(){
for(int i=; i<L; i++){
cout << "id = " << i << " " << "fail = " << fail[i] << " " << "end = " << end[i] << endl;
for(int j=; j<; j++){
cout << next[i][j] << " ";
}
cout << endl;
}
}
};
char buf[];
Trie ac;
int main(){
freopen("in.txt", "r", stdin);
int T;
scanf("%d", &T);
while(T--){
int n;
scanf("%d", &n);
ac.init();
for(int i=; i<n; i++){
scanf("%s", buf);
ac.insert(buf);
}
ac.build();
scanf("%s", buf);
cout << buf << endl;
printf("%d\n", ac.query(buf));
}
return ;
}

C - L语言

我把学习心得放在这里了

D - Computer Virus on Planet Pandora

题意:给一个文本串,求其正反两个串中出现了几个模式串。

这里AC自动机中query()操作的作用:查询Trie中有几个前缀出现在文本串中。

方法是遍历Trie树找val=1的点即字符串最后一个字符,然后计数,同时要通过fail找回边,这里就是kmp的意味了。

所以才有人说AC自动机 = Trie+Kmp。

 #include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
#define mst(s, t) memset(s, 0, sizeof(s));
struct Trie{
int ch[][], fail[], val[];
int res, sz;
inline void init(){ sz=; mst(ch[], ); mst(val, ); }
inline int idx(char c) { return c-'A'; }
inline void insert(char *s){
int u = , n = strlen(s);
for(int i=; i<n; i++){
int c = idx(s[i]);
if(!ch[u][c]){
mst(ch[sz], );
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = ;
}
inline void build(){
queue<int> Q;
int u; fail[] = ;
for(int i=; i<; i++){
u = ch[][i];
if(u){ fail[u]=; Q.push(u); }
}
while( !Q.empty() ){
int now = Q.front(); Q.pop();
for(int i=; i<; i++){
u = ch[now][i];
if(!u){ ch[now][i] = ch[fail[now]][i]; continue; }
Q.push(u);
int v = fail[now];
while(v && !ch[v][i]) v=fail[v];
fail[u] = ch[v][i];
}
}
}
inline int query(char *buf, int len){
int u = , res = ;
for(int i=; i<len; i++){
int c = idx(buf[i]);
u = ch[u][c];
int temp = u;
//有几个前缀出现在文本串中
while(temp && val[temp]!=){
res += val[temp];
val[temp] = ;
temp = fail[temp];
}
}
return res;
}
};
char tmp[], buf[];
Trie ac;
int main(){
// freopen("in.txt", "r", stdin);
int T;
scanf("%d", &T);
while(T--){
ac.init();
int n;
scanf("%d", &n);
for(int i=; i<n; i++){
scanf("%s", buf);
ac.insert(buf);
}
ac.build();
scanf("%s", tmp);
int tlen = strlen(tmp), blen=;
for(int i=; i<tlen; i++){
if(tmp[i]=='['){
i++;
int cnt = ;
while(tmp[i] >= '' && tmp[i] <= ''){
cnt = cnt* + tmp[i++]-'';
}
for(int k=; k<cnt; k++){
buf[blen++] = tmp[i];
}
i++;
}else{
buf[blen++] = tmp[i];
}
}
tmp[blen] = '\0';
for(int i=; i<blen; i++){
tmp[i] = buf[blen-i-];
}
buf[blen] = '\0';
printf("%d\n", ac.query(buf, blen) + ac.query(tmp, blen));
}
return ;
}

E - Family View

题意:找出文本串中所有模式串。

AC自动机的变化在insert()和find(),比较难想的应该是如何结合题意利用find()。

上题是计数模式串出现在文本串中的个数,而这道题则是每个模式串出现在文本串中的位置。

那么重点就是如何标记这些位置。

这题我用的是训练指南上的代码风。

 #include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cctype>
using namespace std;
#define mst(s, t) memset(s, t, sizeof(s))
const int maxnode = 1e6+;
const int sigma_size = ;
struct Trie{
int ch[maxnode][sigma_size], val[maxnode], f[maxnode], last[maxnode], sz;
int cnt[maxnode], dis[maxnode];
inline void init(){ sz=; mst(ch[], ); mst(val, ); mst(cnt, ); }
inline int idx(char x){ return tolower(x)-'a'; }
inline void insert(char *s){
int u = , n = strlen(s);
for(int i=; i<n; i++){
int c = idx(s[i]);
if(!ch[u][c]){
mst(ch[sz], );
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = ;
dis[u] = n;
}
inline void get_fail(){
queue<int> Q;
int u; f[] = ;
for(int c=; c<sigma_size; c++){
u = ch[][c];
if(u) { last[u] = f[u]=; Q.push(u); }
}
while(!Q.empty()){
int now = Q.front(); Q.pop();
for(int c=; c<sigma_size; c++){
u = ch[now][c];
if(!u) { ch[now][c]=ch[f[now]][c]; continue; }
Q.push(u);
int v = f[now];
while(v && !ch[v][c]) v = f[v];
f[u] = ch[v][c];
last[u] = val[f[u]] ? f[u] : last[f[u]];
//{(he), (she)}5 --> {(he)}2
//last[5] = val[2]? 2 : last[f[2]]
//last[5] = 1 ? 2 : 0
//last[i]:表示沿着失配指针往回走时遇到的下一个单词结点编号
}
}
}
inline void find(char *s){
int n = strlen(s), u = ;
for(int i=; i<n; i++){
if(!isalpha(s[i]))continue; //这里要写,空格等乱七八糟的东西太多
int c = idx(s[i]);
u = ch[u][c];
if(val[u]) prin(i, u); //在字符串中找到前缀树中的内容后对字符串中的信息进行标记
else if(last[u]) prin(i, last[u]);
}
}
inline void prin(int i, int j){
if(j){
++cnt[i+];
--cnt[i+-dis[j]]; //[i+1-len, i+1)所有字符要forbid,故标记首尾
//cout << "i = " << i<< " last["<<j<<"] = "<<last[j]<< endl;
/*
* 这里last数组和fail数组回跳的方式有什么区别 和 它是如何提高效率的,
* 若有前辈知道,望直接指教,感激不尽。
*/
prin(i, last[j]);
}
}
/*
inline void show(){
for(int i=0; i<sz; i++){
cout<<"last["<<i<<"] = "<<last[i]<<" f["<<i<<"] = "<<f[i]<<endl;
}
}
*/
};
Trie ac;
char s[maxnode];
int main(){
freopen("in.txt", "r", stdin);
int t;
scanf("%d", &t);
while(t--){
ac.init();
int n;
scanf("%d", &n);
while(n--){
scanf("%s", s);
ac.insert(s);
}
ac.get_fail();
getchar();
fgets(s, maxnode, stdin);
ac.find(s);
int ans = , len = strlen(s);
for(int i=; i<len; i++){
ans += ac.cnt[i];
if(ans < ) putchar('*');
else putchar(s[i]);
}
}
return ;
}

还有就是我有一个疑惑想了很久没有解决,也找了万老师解惑,虽然并没有得到很令我心服的答案,但依然很感谢万老师的指导。

训练指南245页有关last指针的分析,他说后缀链接last[j]是结点j沿着失配指针往回走。我的疑惑是:这个last指针和fail有什么区别,或是二者回跳方式有何不同。如果有哪位聚聚或老师对这个问题有过思考请不吝指教,或指导下学习方式等,本菜鸡定感激不尽。

AC自动机--summer-work之我连模板题都做不出的更多相关文章

  1. 后缀数组--summer-work之我连模板题都做不起

    这章要比上章的AC自动机要难理解. 这里首先要理解基数排序:基数排序与桶排序,计数排序[详解] 下面通过这个积累信心:五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码) 下面认真研读下这篇: [转 ...

  2. 【luogu P3808 AC自动机(简单版)】 模板

    题目链接:https://www.luogu.org/problemnew/show/P3808 #include <queue> #include <cstdio> #inc ...

  3. 「kuangbin带你飞」专题十七 AC自动机

    layout: post title: 「kuangbin带你飞」专题十七 AC自动机 author: "luowentaoaa" catalog: true tags: - ku ...

  4. P3808 【模板】AC自动机(简单版)

    题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...

  5. AC自动机详解 (P3808 模板)

    AC自动机笔记 0.0 前言 哇,好久之前就看了 KMP 和 Trie 树,但是似乎一直没看懂 AC自动机?? 今天灵光一闪,加上之前看到一些博客和视频,瞬间秒懂啊... 其实这个玩意还是蛮好理解的. ...

  6. [AC自动机]【学习笔记】

    Keywords Search Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)To ...

  7. AC自动机入门

    Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. KMP算法很好的解决了单模式匹配问题,如果有了字典树的基础,我们可以完美的结合二者解决多 ...

  8. 【暑假】[实用数据结构] AC自动机

    Aho-Corasick自动机  算法: <功能> AC自动机用于解决文本一个而模板有多个的问题. AC自动机可以成功将多模板匹配,匹配意味着算法可以找到每一个模板在文本中出现的位置. & ...

  9. hdu 1277 AC自动机入门(指针版和数组版)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1277 推荐一篇博客(看思路就可以,实现用的是java): https://www.cnblogs.co ...

随机推荐

  1. Pycharm建立web2py项目

    web2py是一种免费的,开源的web开发框架,用于敏捷地开发安全的,数据库驱动的web应用:web2p采用Python语言编写,并且可以使用Python编程.web2py是一个完整的堆栈框架,也就是 ...

  2. python内置函数(二)之filter,map,sorted

    filter filter()函数接收一个函数 f 和一个iterable的对象,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条 ...

  3. Ubuntu本地软件源制作

    操作 获取需要的deb包 #执行安装后,安装的包会保存在/var/cache/apt/archives 目录下 apt-get install vim #查看 正在处理用于 man-db (2.8.7 ...

  4. IDEA配置和插件

    1.相关配置 设置字体和大小 2.插件 maven helper 解决maven包冲突的问题 打开pom文件,并可以切换tab,简单使用,如下图 RestfulToolkit RestfulToolk ...

  5. windows系统下node-gyp的配置使用

    1.安装python和vs2017 安装python要将python命令配置到系统变量path 也可以通过npm i -g windows-build-tools来安装 2.查看和设置npm conf ...

  6. Laravel 项目中事件控制的体会--综合应用 trait 多态

    1 我们想像有这样的需求 1.1 应用中有两个类.其一是 荣誉(Honour)其一是 档案(Archive)Honour 和 Arhcive 是多态关联.即拥有档案属性的不只荣誉类,还有更多的类去关联 ...

  7. Bzoj 1798: [Ahoi2009]Seq 维护序列seq(线段树区间操作)

    1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec Memory Limit: 64 MB Description 老师交给小可可一个维护数列的任务,现在小可 ...

  8. Codevs 1105 过河 2005年NOIP全国联赛提高组

    1105 过河 2005年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 在河上有一座独木桥,一只青蛙 ...

  9. jQuery系列(八):jQuery的位置信息

    1.宽度和高度 (1):获取宽度 .width() 描述:为匹配的元素集合中获取第一个元素的当前计算宽度值.这个方法不接受任何参数..css(width) 和 .width()之间的区别是后者返回一个 ...

  10. jQuery系列(三):jQuery动画效果

    jQuery提供的一组网页中常见的动画效果,这些动画是标准的.有规律的效果:同时还提供给我们了自定义动画的功能. 1.显示动画 方式一: $("div").show(); 解释:无 ...