AC自动机--summer-work之我连模板题都做不出
这章对现在的我来说有点难,要是不写点东西,三天后怕是就一无所有了。
但写这个没有营养的blog的目的真的不是做题或提升,只是学习学习代码和理解一些概念。
现在对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之我连模板题都做不出的更多相关文章
- 后缀数组--summer-work之我连模板题都做不起
这章要比上章的AC自动机要难理解. 这里首先要理解基数排序:基数排序与桶排序,计数排序[详解] 下面通过这个积累信心:五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码) 下面认真研读下这篇: [转 ...
- 【luogu P3808 AC自动机(简单版)】 模板
题目链接:https://www.luogu.org/problemnew/show/P3808 #include <queue> #include <cstdio> #inc ...
- 「kuangbin带你飞」专题十七 AC自动机
layout: post title: 「kuangbin带你飞」专题十七 AC自动机 author: "luowentaoaa" catalog: true tags: - ku ...
- P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
- AC自动机详解 (P3808 模板)
AC自动机笔记 0.0 前言 哇,好久之前就看了 KMP 和 Trie 树,但是似乎一直没看懂 AC自动机?? 今天灵光一闪,加上之前看到一些博客和视频,瞬间秒懂啊... 其实这个玩意还是蛮好理解的. ...
- [AC自动机]【学习笔记】
Keywords Search Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)To ...
- AC自动机入门
Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. KMP算法很好的解决了单模式匹配问题,如果有了字典树的基础,我们可以完美的结合二者解决多 ...
- 【暑假】[实用数据结构] AC自动机
Aho-Corasick自动机 算法: <功能> AC自动机用于解决文本一个而模板有多个的问题. AC自动机可以成功将多模板匹配,匹配意味着算法可以找到每一个模板在文本中出现的位置. & ...
- hdu 1277 AC自动机入门(指针版和数组版)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1277 推荐一篇博客(看思路就可以,实现用的是java): https://www.cnblogs.co ...
随机推荐
- Event Binding in Angular
https://www.pluralsight.com/guides/angular-event-binding Introduction In this guide, we will explore ...
- MySQL 5.6, 5.7, 8.0版本的新特性汇总大全
转载:http://blog.itpub.net/15498/viewspace-2650661/ MySQL 5.6 1).支持GTID复制 2).支持无损复制 3).支持延迟复制 4).支持基于库 ...
- Windows下安装配置Apache+PHP+Mysql环境
1.下载相关安装包 Apache下载: http://archive.apache.org/dist/httpd/binaries/win32/ ,选择httpd-2.2.25-win32-x86-n ...
- Java web中不同浏览器间导出Excel文件名称乱码问题解决方案
问题描述: 对于不同浏览器存在对中文编码格式问题,从而在导出Excel文件时,中文文件名出现乱码的情况,即在程序中给要导出的文件指定一个中文名字时,在浏览器上出现的下载框中的文件名出现了乱码,解决如下 ...
- 从ORM框架到SQLAlchemy
一.ORM 1.什么是ORM 对象-关系映射(Object-Relational Mapping,简称ORM),面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中 ...
- String字符串反转
new StringBuffer("abcde").reverse().toString(); 通过char数组进行转换 package com.test.reverse; pub ...
- 2018-2019 ACM-ICPC Brazil Subregional Programming Contest B. Marbles(博弈)
题目链接:https://codeforc.es/gym/101908/problem/B 题意:两个人玩游戏,有 n 块石头,初始坐标为(x,y),一次操作可以将一块石头移动到(x - u,y),( ...
- CloseableHttpClient设置超时
Java开发我们常常需要和第三方系统进行通信,通信的方式有多种,如dubbo方式,webservice,微服务和CloseableHttpClient等方式,常涉及到超时问题,这里主要说的是Close ...
- springboot2.0入门(五)--swagger2接口API构建
一.特点 代码变,文档变.只需要少量的注解,Swagger 就可以根据代码自动生成 API 文档,很好的保证了文档的时效性. 跨语言性,支持 40 多种语言. Swagger UI 呈现出来的是一份可 ...
- 题解 [CF916E] Jamie and Tree
题面 解析 这题考试时刚了四个小时. 结果还是爆零了 主要就是因为\(lca\)找伪了. 我们先考虑没有操作1,那就是裸的线段树. 在换了根以后,主要就是\(lca\)不好找(分类讨论伪了). 我们将 ...