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

但写这个没有营养的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. Java中的集合List、ArrayList、Vector、Stack(三)

    List接口 List集合代表一个有序集合,集合中每一个元素都有其对应的顺序索引.List集合容许使用重复元素,可以通过索引来访问指定位置的集合对象. ArrayList和Vector实现类 Arra ...

  2. org.apache.commons.io.FilenameUtils 常用的方法

    /** * getExtension * 获取文件的后缀名 */ public static void testGetExtension() { String extension = Filename ...

  3. HDOJ4467 ( 分块 思想 )

    题目:链接:http://acm.hdu.edu.cn/showproblem.php?pid=4467 题意:给你n个点(每个点都有一个颜色,0代表黑色,1代表白色),m条边,每条边有一个权值.现在 ...

  4. docker部署项目: centos+python+redis+mysql+uwsgi+nginx

    一.Centos7安装docker 1.1 环境配置 先测试是否下载了docker:查看镜像:docker images没有下载,就依次执行以下环境的安装 ①curl http://mirrors.a ...

  5. Python常用模块之hashlib模块

    1.hashilib模块的功能 python的hashlib提供了常见的摘要算法,如MD5, SHA1等等. 什么是摘要算法呢?摘要算法又称哈希算法.散列算法.它通过一个函数,把任意长度的数据转换成一 ...

  6. codeforces#1108E2. Array and Segments (线段树+扫描线)

    题目链接: http://codeforces.com/contest/1108/problem/E2 题意: 给出$n$个数和$m$个操作 每个操作是下标为$l$到$r$的数减一 选出某些操作,使$ ...

  7. Mybatis源码学习之类型转换(四)

    简述 JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集中获取数据时,则需要从 ...

  8. CF280C

    CF280C ZR补题计划 题意: 一棵有根树,每次选择一个未删除的节点,然后删除它和它的子树内的点,问期望删多少次可以把整个树删完 解析: 显然,通过题面,我们可以知道对于一个点对 $ (u,v) ...

  9. Linux设备驱动程序 之 模块参数

    模块支持参数的方法 内核允许驱动程序指定参数,这些参数可在运行insmod或者modprobe命令装载模块时赋值,modprobe还可以从它的配置文件(/etc/modporb.conf)中读取参数值 ...

  10. np数组转换函数

    1.多维数组降为一维: a = np.arange(24) np.ravel(a)或者a.ravel a.flatten 2.数据类型转换 a = a.astype(np.float32) //tf是 ...