首先看第一题,一道DP+字典树的题目,具体中文题意和题解见训练指南209页。

初看这题模型还很难想,看过蓝书提示之后发现,这实际上是一个标准DP题目:通过数组来储存后缀节点的出现次数。也就是用一颗字典树从后往前搜一发。最开始觉得这种搞法怕不是要炸时间,当时算成了O(N*N)毕竟1e5的数据不搞直接上N*N的大暴力。。。后来发现,字典树根本跑不完N因为题目限制字典树最多右100层左右。

实际上这道题旧思想和模型来说很好(因为直观地想半天还真想不出来。。)但是实际实现起来很简单——撸一发字典树就好了。然而专门写一篇博客是因为自从学了刘汝佳的字典树之后就发现之前自己写的那个实在是太不优雅(使用了大量指针,还牵扯到内存回收的鬼故事),反而不如刘汝佳这种,一个类搞定一切,方便快捷,也不会因为莫名的bug调试一下午什么的。。于是来说说刘汝佳字典树的实现方式:

  1. 一个二维数组,cha【MAXN】【SIGMA_SIZE】用来存子节点的位置
  2. 一个标记数组,val【MAXN】用来储存每个节点的相关信息,比如是不是单词的结尾、第几次出现等
  3. 一个变量,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自动机首先是一颗字典树——加了失配边和后缀指针的字典树。

因而在上述字典树的基础上应当加入:

  1. f【MAXN】表示适配函数
  2. 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自动机的更多相关文章

  1. HDU 5384 字典树、AC自动机

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=5384 用字典树.AC自动机两种做法都可以做 #include<stdio.h> #includ ...

  2. 【AC自动机】【字符串】【字典树】AC自动机 学习笔记

    blog:www.wjyyy.top     AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维.     AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...

  3. [知识点]Trie树和AC自动机

    // 此博文为迁移而来,写于2015年5月27日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w1s8.html 1.前 ...

  4. 算法笔记--字典树(trie 树)&& ac自动机 && 可持久化trie

    字典树 简介:字典树,又称单词查找树,Trie树,是一种树形结构,是哈希树的变种. 优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较. 性质:根节点不包含字符,除根节点外每一个 ...

  5. 字典树基础进阶全掌握(Trie树、01字典树、后缀自动机、AC自动机)

    字典树 概述     字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它 ...

  6. 中文分词系列(二) 基于双数组Tire树的AC自动机

    秉着能偷懒就偷懒的精神,关于AC自动机本来不想看的,但是HanLp的源码中用户自定义词典的识别是用的AC自动机实现的.唉-没办法,还是看看吧 AC自动机理论 Aho Corasick自动机,简称AC自 ...

  7. [HNOI2004]L语言 trie树? Ac自动机? hash!!

    题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构成.一个单词W也是由若干小写字母构成.一个字典D是若干个单词的 ...

  8. 【uva1502/hdu4117-GRE Words】DP+线段树优化+AC自动机

    这题我的代码在hdu上AC,在uva上WA. 题意:按顺序输入n个串以及它的权值di,要求在其中选取一些串,前一个必须是后一个的子串.问d值的和最大是多少. (1≤n≤2×10^4 ,串的总长度< ...

  9. Trie树&kmp&AC自动机&后缀数组&Manacher

    Trie 计数+Trie,读清题意很重要 https://vjudge.net/problem/UVALive-5913 kmp AC自动机 模板:https://vjudge.net/problem ...

随机推荐

  1. Go 微服务实践

    http://www.open-open.com/lib/view/open1473391214741.html

  2. nginx 中 root和alias

    根本区别 一个请求的url= http://ip:port/path 在location中配置root和alias的区别: root是在location的正则之前拼接了路径 alias是在locati ...

  3. Nodejs入门边读边想边记(-)

    Node入门>>一本全面的Node.js教程网站地址:http://www.nodebeginner.org/index-zh-cn.html 本文记录我在阅读上面这个网站的过程中得到的一 ...

  4. 1)实际时间(real time): 从command命令行开始执行到运行终止的消逝时间; 2)用户CPU时间(user CPU time): 命令执行完成花费的用户CPU时间,即命令在用户态中执行时间总和; 3)系统CPU时间(system CPU time): 命令执行完成花费的系统CPU时

    1)实际时间(real time): 从command命令行开始执行到运行终止的消逝时间: 2)用户CPU时间(user CPU time): 命令执行完成花费的用户CPU时间,即命令在用户态中执行时 ...

  5. vue组件总结(三)

    一.什么是组件 组件(component)是Vue最强大的功能之一.组件可以扩展HTML元素,封装可重用的代码,根据项目需求,抽象出一些组件,每个组件里包含了展现.功能和样式.每个页面,根据自己的需要 ...

  6. LeetCode ZigZag Conversion(将字符串排成z字型)

    class Solution { public: string convert(string s, int nRows) { string a=""; int len=s.leng ...

  7. DDL与DML语句

    1. DDL语句 SQL语句:结构化查询语句,使用SQL与数据库“沟通”,完成相应的数据库操作. l DDL:数据定义语言,用来维护数据库对象 1.1 创建表 Ø CREATE:创建表 演示:创建员工 ...

  8. C语言——字符串长度的计算方法

    1.不带转义字符的字符串 如:“abc!x=/”,其长度为7 2.带转义字符的字符串 (1) 字符串“abc\n”:其中的'\n'为转义字符(换行符),计算字符串长度时只能计作一个字符,所以该字符串的 ...

  9. Java中的字符串问题

    本文章分为三个部分: 1.创建字符串对象的两种方式以及它们的存储方式 2.String a = new String("a")创建了几个对象的问题 3.字符串小例子 ------- ...

  10. 3205: 数组做函数参数--数组元素求和1--C语言

    3205: 数组做函数参数--数组元素求和1--C语言 时间限制: 1 Sec  内存限制: 128 MB提交: 178  解决: 139[提交][状态][讨论版][命题人:smallgyy] 题目描 ...