首先看第一题,一道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. Json数组对象和对象数组

    Json的简单介绍 从结构上看,所有的数据最终都可以分成三种类型: 第一种类型是scalar(标量),也就是一个单独的string(字符串)或数字(numbers),比如“北京”这个单独的词. 第二种 ...

  2. 搭建Node.js Redis开发环境

    创建项目 初始化为node项目 $npm init   安装redis   安装@types/node, @types/redis, typescript   初始化TypeScript   配置ts ...

  3. spring增强

    1.前置增强 接口:ISomeService public interface ISomeService { public void doSome(); } 类 public class MyBefo ...

  4. idea(2017.3)的安装及快捷键的配置

    一  破解安装步骤: 到IntelliJ IDEA的官网:www.jetbrains.com 然后找到下载的地方,选择自己电脑所匹配的下载安装包,这里我们选择收费版的下载 接着,点击安装包进行安装,按 ...

  5. Centos_linux系统的区别及实际查看

    在Linux系统查看系统版本为 32 位还是 64 位 [root@localhost ~]# cat /etc/redhat-release CentOS release 6.8 (Final) [ ...

  6. PHP线程安全和非线程安全有什么区别

    我们先来看一段PHP官网的原话: Which version do I choose? IIS If you are using PHP as FastCGI with IIS you should ...

  7. JSP学习 三大指令、九个内置对象、JavaBean、EL表达式

    1.page-->最复杂:<%@page language="java" info="xxx"...%> * pageEncoding和con ...

  8. 梦织未来Windows驱动编程 第05课 小结(读取另一驱动,遍历所有驱动)

    读取另一驱动 驱动通过"\\Driver\\XueTr"获取到了XueTr工具的驱动,并Hook了XueTr驱动的分发函数. 具体的驱动代码如下: //FilterDriver.c ...

  9. 为Visual Studio 2012添加MSDN离线帮助

    之前有网络的情况下,一直使用的都是在线的,最近又有笔记本上面有时使用时没有网络,所以就想使用下离线的MSDN包.可是找了半天,发现都是需要再次进行下载的.VS2012使用的帮助程序是HelpViewe ...

  10. 【洛谷3157】[CQOI2011] 动态逆序对(CDQ分治)

    点此看题面 大致题意: 给你一个从\(1\)到\(n\)的排列,问你每次删去一个元素后剩余的逆序对个数. 关于\(80\)分的树套树 为了练树套树,我找到了这道题目. 但悲剧的是,我的 线段树套\(T ...