LA_3942 LA_4670 从字典树到AC自动机
首先看第一题,一道DP+字典树的题目,具体中文题意和题解见训练指南209页。
初看这题模型还很难想,看过蓝书提示之后发现,这实际上是一个标准DP题目:通过数组来储存后缀节点的出现次数。也就是用一颗字典树从后往前搜一发。最开始觉得这种搞法怕不是要炸时间,当时算成了O(N*N)毕竟1e5的数据不搞直接上N*N的大暴力。。。后来发现,字典树根本跑不完N因为题目限制字典树最多右100层左右。
实际上这道题旧思想和模型来说很好(因为直观地想半天还真想不出来。。)但是实际实现起来很简单——撸一发字典树就好了。然而专门写一篇博客是因为自从学了刘汝佳的字典树之后就发现之前自己写的那个实在是太不优雅(使用了大量指针,还牵扯到内存回收的鬼故事),反而不如刘汝佳这种,一个类搞定一切,方便快捷,也不会因为莫名的bug调试一下午什么的。。于是来说说刘汝佳字典树的实现方式:
- 一个二维数组,cha【MAXN】【SIGMA_SIZE】用来存子节点的位置
- 一个标记数组,val【MAXN】用来储存每个节点的相关信息,比如是不是单词的结尾、第几次出现等
- 一个变量,size起到类似于栈顶指针的作用。
整体上,训练指南的字典树实现方案类似于一个大型栈,开开之后就一路往进压元素就好了。因而插入节点的时候很容易联想到入栈的过程。同时,整个字典树初始化时的常数也很小——不需要回收整棵字典树,只需要讲字典树的根节点指针置零、栈指针size置一就好;在每次增加元素的时候也只需要把当前元素的指针提前置零即可。
下面放AC代码:
#include<bits/stdc++.h>
using namespace std; const long long MAXN=;
char str[MAXN];
long long len=;
long long dp[MAXN];
const long long MOD=; class AC_AUTO
{
public:
long long cha[MAXN][];
long long f[MAXN];
long long last[MAXN];
long long val[MAXN];
long long size; AC_AUTO()
{
init();
}
void init()
{
memset(cha[],,sizeof(cha[])); //避免大规模初始化浪费时间
size=;
// memset(val,0,sizeof(val));
} void insert(char *tar)
{
int len=strlen(tar);
int u=;
for(int i=;i<len;++i)
{
if(!cha[u][tar[i]-'a'])
{
memset(cha[size],,sizeof(cha[size]));
val[size]=;
cha[u][tar[i]-'a']=size;
size++;
}
u=cha[u][tar[i]-'a'];
}val[u]=;
}
bool find(char *tar)
{
int l=strlen(tar);
int u=;int p1=len-l;
for(int i=;i<l;++i)
{
if(!cha[u][tar[i]-'a'])return false;
u=cha[u][tar[i]-'a'];
if(val[u])
{
dp[p1]+=dp[p1+i+];
dp[p1]%=MOD;
}
}return val[u];
}
};AC_AUTO t1;
long long kk=; void init()
{
memset(dp,,sizeof(dp));
t1.init();
len=strlen(str);
long long n;
cin>>n;
for(int i=;i<n;++i)
{
char sub[];
cin>>sub;
t1.insert(sub);
}
dp[len]=;
for(int i=len-;i>=;--i)
{
t1.find(str+i);
}
cout<<"Case "<<kk++<<": "<<dp[]<<"\n";
}
int main()
{
cin.sync_with_stdio(false);
while(cin>>str)init(); return ;
}
事实上我写第一题主要是为了在第一题的基础上实现后面刘汝佳规约的AC自动机,于是上面代码的类名依然是AC_AUTO。刘汝佳规约的AC自动机首先是一颗字典树——加了失配边和后缀指针的字典树。
因而在上述字典树的基础上应当加入:
- f【MAXN】表示适配函数
- last【MAXN】表示失配函数中的最近一个单词节点(VAL【】不为零)
AC自动机在功能上应当是一个多重KMP,因而从原理上认为实现方式上应当等同于KMP——按照出现顺序向后遍历并在该过程中不断寻找失配边。于是考虑字典树情况,也应当按照层数逐渐递增的形式进行匹配,因而认为BFS很合适实现这个算法——(实现树的层次遍历),于是建立失配边的过程类似基本类似于KMP+BFS
本体有些坑在于数组尺寸的调教,如果没整好。。。就地TLE。。(不是数组越界是T。。)
另外训练指南中推荐使用map来保存字符串的出现顺序以避免重复情况,但是考虑到map直接使用【】来进行操作有比较大的常数,考虑到本身AC自动机就是一个字典树,于是强行在字典树中查询可能结果会更好。
然而。。。做了这个优化之后并没有发现实质的效率提升。。都是46毫秒。。。
#include<bits/stdc++.h>
using namespace std; const long long MAXN=*+;
const long long SIGMA_SIZE=;
char str[];
char input[][];
long long cnt[];
long long len=,n=;
const long long MOD=;
map<string,int> ms;
//char anss[1000233];
class AC_AUTO
{
public:
long long cha[MAXN][SIGMA_SIZE];
long long f[MAXN];
long long last[MAXN];
long long val[MAXN];
long long size; AC_AUTO()
{
init();
}
void init()
{
memset(cha[],,sizeof(cha[])); //避免大规模初始化浪费时间
size=;
// memset(val,0,sizeof(val));
} void insert(char *tar,int numb)
{
int len=strlen(tar);
int u=;
for(int i=;i<len;++i)
{
if(!cha[u][tar[i]-'a'])
{
memset(cha[size],,sizeof(cha[size]));
val[size]=;
cha[u][tar[i]-'a']=size;
size++;
}
u=cha[u][tar[i]-'a'];
}val[u]=numb;//ms[string(tar)]=numb;
}
void print(int j)
{
if(j)
{
cnt[val[j]]++;
print(last[j]);
}
}
void find(char *tar)
{
int n=strlen(tar);
int j=;
for(int i=;i<n;++i)
{
int c=tar[i]-'a';
while(j&& !cha[j][c])j=f[j];
j=cha[j][c];
if(val[j])print(j);
else if(last[j])print(last[j]);
}
}
void getfail()
{
queue<int> q;
f[]=;
for(int c=;c<SIGMA_SIZE;++c)
{
int u=cha[][c];
if(u)
{
f[u]=;q.push(u);
last[u]=;
}
}
while(!q.empty())
{
int r=q.front();q.pop();
for(int c=;c<SIGMA_SIZE;++c)
{
int u=cha[r][c];
if(!u)continue;
q.push(u);
int v=f[r];
while(v&&!cha[v][c])v=f[v];
f[u]=cha[v][c];
last[u]= val[f[u]]? f[u]:last[f[u]]; }
}
}
long long get(char *tar )
{
int l=strlen(tar );
int u=;
for(int i=;i<l;++i)
{
u=cha[u][tar[i]-'a'];
}
return val[u];
} };AC_AUTO a1;
void init()
{
memset(cnt,,sizeof(cnt));
// ms.clear();
a1.init();
for(int i=;i<=n;++i)
{
scanf("%s",input[i]);
a1.insert(input[i],i);
}
a1.getfail();
scanf("%s",str);
a1.find(str);
long long ans=-;
for(int i=;i<=n;++i)
{
if(cnt[i]>ans)ans=cnt[i];
}
printf("%lld\n",ans);
for(int i=;i<=n;++i)
{
if(cnt[a1.get(input[i])]==ans)printf("%s\n",input[i]);
// else cout<<"not "<<input[i]<<ends<<cnt[ms[string(input[i])]]<<endl;
} }
int main()
{
// cin.sync_with_stdio(false); while(scanf("%lld",&n)==&&n)init(); return ;
}
LA_3942 LA_4670 从字典树到AC自动机的更多相关文章
- HDU 5384 字典树、AC自动机
题目:http://acm.hdu.edu.cn/showproblem.php?pid=5384 用字典树.AC自动机两种做法都可以做 #include<stdio.h> #includ ...
- 【AC自动机】【字符串】【字典树】AC自动机 学习笔记
blog:www.wjyyy.top AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维. AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...
- [知识点]Trie树和AC自动机
// 此博文为迁移而来,写于2015年5月27日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w1s8.html 1.前 ...
- 算法笔记--字典树(trie 树)&& ac自动机 && 可持久化trie
字典树 简介:字典树,又称单词查找树,Trie树,是一种树形结构,是哈希树的变种. 优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较. 性质:根节点不包含字符,除根节点外每一个 ...
- 字典树基础进阶全掌握(Trie树、01字典树、后缀自动机、AC自动机)
字典树 概述 字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它 ...
- 中文分词系列(二) 基于双数组Tire树的AC自动机
秉着能偷懒就偷懒的精神,关于AC自动机本来不想看的,但是HanLp的源码中用户自定义词典的识别是用的AC自动机实现的.唉-没办法,还是看看吧 AC自动机理论 Aho Corasick自动机,简称AC自 ...
- [HNOI2004]L语言 trie树? Ac自动机? hash!!
题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构成.一个单词W也是由若干小写字母构成.一个字典D是若干个单词的 ...
- 【uva1502/hdu4117-GRE Words】DP+线段树优化+AC自动机
这题我的代码在hdu上AC,在uva上WA. 题意:按顺序输入n个串以及它的权值di,要求在其中选取一些串,前一个必须是后一个的子串.问d值的和最大是多少. (1≤n≤2×10^4 ,串的总长度< ...
- Trie树&kmp&AC自动机&后缀数组&Manacher
Trie 计数+Trie,读清题意很重要 https://vjudge.net/problem/UVALive-5913 kmp AC自动机 模板:https://vjudge.net/problem ...
随机推荐
- 解决mac下安装yeoman时没有权限问题
在mac下安装yeoman经常会出现如下图错误: 解决办法:在命令行执行-- sudo chown -R $USER /usr/local/lib/node_modules 回车就OK
- Ini文件格式说明
http://www.cnblogs.com/CUIT-DX037/ 百度百科介绍:ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储 ...
- jstack的使用方法
背景 记得前段时间,同事说他们测试环境的服务器cpu使用率一直处于100%,本地又没有什么接口调用,为什么会这样?cpu使用率居高不下,自然是有某些线程一直占用着cpu资源,那又如何查看占用cpu较高 ...
- java基础概念整理综合 及补充(jdk1.8)
2018 java基础 笔记回顾摘要 一 1,html 与 注释: <!-- --> 注释不能嵌套 代码都得有注释. 2,空格符: 3,css选择的优先级: id选择器 > ...
- pure响应式布局
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...
- Get query parameter from url
URL = { getUrlParams: function ( name, url ) { if (!url) url = window.location.href; name = name.rep ...
- sublime package control以及常用插件
一.package Control安装 1.sublime 3 import urllib.request,os; pf = 'Package Control.sublime-package'; ip ...
- html5shiv.js的作用是
解析 html5shiv主要解决HTML5提出的新的元素不被IE6-8识别,这些新元素不能作为父节点包裹子元素,并且不能应用CSS样式.让CSS 样式应用在未知元素上只需执行 document.cre ...
- python换行
python中如果一行代码太长,看着不方便时,怎么办? 只需要在需要换行的地方添加上符号 \ 就行了.
- 初尝微信小程序2-Swiper组件、导航栏标题配置
swiper 滑块视图容器. 很多网页的首页都会有一个滚动的图片模块,比如天猫超市首页,滚动着很多优惠活动的图片,用来介绍优惠内容,以及供用户点击快速跳转到相应页面. Swiper不仅可以滚动图片,也 ...