首先看第一题,一道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. hibernate课程 初探单表映射2-4 transaction简介

    1 hibernate是非自动提交.如果transaction不写的话,会只创建表结构而不插入语句.   如果不写transaction而想实现插入的功能的话,需要重写session的dowork方法 ...

  2. agc007B - Construct Sequences(构造)

    题意 题目链接 给出一个$1-N$的排列$P$,构造两个数组$a, b$满足 Sol 发现我的水平也就是能做一做0-699的题.... 直接构造两个等差数列$a, b$,公差为$20000$ 然后从小 ...

  3. php编码转换相关

    iconv (PHP 4 >= 4.0.5, PHP 5, PHP 7) iconv — 字符串按要求的字符编码来转换 string iconv ( string $in_charset , s ...

  4. Kyligence Analytics Platform Enterprise

    平台: arm 类型: ARM 模板 软件包: kap 2.3 kyanalyzer 2.3 apache kylin basic software bi big data cube data war ...

  5. display:table的几个用法(元素平分宽度,垂直居中)

    DIV+CSS的布局已经让表格布局几乎很少用到,除非表格语义性很强的情况. display:table解决了一部分需要使用表格特性但又不需要表格语义的情况, 尤其是DIV+CSS很不方便解决的问题,比 ...

  6. 300行ABAP代码实现一个最简单的区块链原型

    不知从什么时候起,区块链在网上一下子就火了. 这里Jerry就不班门弄斧了,网上有太多的区块链介绍文章.我的这篇文章没有任何高大上的术语,就是300行ABAP代码,实现一个最简单的区块链原型. 我个人 ...

  7. Hybris UI的Route(路由)实现

    登录Hybris前台,在product catalog里选择Digital camera: 点击某个产品进入明细页面: 注意产品明细这个url: 这个明细页面的路由和SAP UI5的路由思路很像. 在 ...

  8. POJ-3436 ACM Computer Factory---最大流+拆点

    题目链接: https://vjudge.net/problem/POJ-3436 题目大意: 每台电脑有p个组成部分,有n个工厂加工电脑.每个工厂对于进入工厂的半成品的每个组成部分都有要求,由p个数 ...

  9. 【洛谷4884】多少个1?(BSGS)

    点此看题面 大致题意: 求满足\(个111...111(N\text{个}1)\equiv K(mod\ m)\)的最小\(N\). 题目来源 这题是洛谷某次极不良心的月赛的\(T1\),当时不会\( ...

  10. 【BZOJ1857】传送带(分治经典:三分套三分)

    点此看题面 大致题意: 一个二维平面上有两条传送带\(AB\)和\(CD\),\(AB\)传送带的移动速度为\(P\),\(CD\)传送带的移动速度为\(Q\),步行速度为\(R\),问你从\(A\) ...