首先看第一题,一道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. Rspec基本语法

    引用链接:http://reverocean.iteye.com/blog/1489957 1. describe和context describe和context方法用来组织相关的行为example ...

  2. FormsAuthentication IsAuthenticated一直为false

    解决办法: 在Web.Config中添加一下红框的内容

  3. 使用JAVA读写Properties属性文件

     使用JAVA读写Properties属性文件 Properties属性文件在JAVA应用程序中是经常可以看得见的,也是特别重要的一类文件.它用来配置应用程序的一些信息,不过这些信息一般都是比较少的数 ...

  4. es6-async

    含义 ES2017 标准引入了 async 函数,使得异步操作变得更加方便. async 函数是什么?一句话,它就是 Generator 函数的语法糖. 前文有一个 Generator 函数,依次读取 ...

  5. python数据类型、输入输出、运算符、条件判断、循环

    变量以及类型 变量:存储程序运行中的数据,变量有3个要素:变量名.变量类型.变量值.python属于弱类型语言,不需要声明变量类型. [root@localhost python]# ipython3 ...

  6. pat乙级1045

    从左到右扫描时记录扫描到当前下标为止的最大值,如果当前元素大于这个最大值,那么它就大于它左边的所有值.同理,从右到左扫描记录扫描到当前下标为止的最小值,如果当前元素小于这个最大小值,那么它就小于它右边 ...

  7. 撸了个 django 数据迁移工具 django-supertube

    撸了个 django 数据迁移工具 django-supertube 支持字段映射和动态字段转化. 欢迎 star,issue https://github.com/FingerLiu/django- ...

  8. World Wind Java开发之四——搭建本地WMS服务器(转)

    在提供地理信息系统客户端时,NASA还为用户提供了开源的WMS Server 服务器应用:World Wind WMS Server.利用这个应用,我们可以架设自己的WMS服务并使用自己的数据(也支持 ...

  9. POJ 3421 X-factor Chains(构造)

    这条链依次乘一个因子.因为n<2^20,sqrt(n)分解因子,相同的因子相对顺序取一个. 组合公式计算一下就好. #include<cstdio> #include<iost ...

  10. POJ 3187 Backward Digit Sums (递推,bruteforce)

    第1行j列的一个1加到最后1行满足杨辉三角,可以先推出组合数来 然后next_permutation直接暴. #include<cstdio> #include<iostream&g ...