洛谷P3796 【模板】AC自动机(加强版)(AC自动机)
洛谷题目传送门
先膜一发yyb巨佬 orz
想学ac自动机的话,推荐一下yyb巨佬的博客,本蒟蒻也是从那里开始学的。
思路分析
裸的AC自动机,这里就不讲了。主要是这题太卡时了,尽管时限放的很大了。。。。。。
用传统方法匹配时,每走到一个新位置,都是要统计答案的。怎么统计呢?暴力跳\(fail\),把沿路上能够产生答案的算上,直到跳到根才停下来。这里的时间复杂度是 \(O(70N)\),在有多组数据的情况下其实是很吃亏的。(蒟蒻用了\(fread\),根据目测,最大的一个点输入大小在\(30MB\)以上!!!)或许这里就是TLE的恶魔。。。。。。
我们发现,在暴跳的过程中,走到的很多节点,对答案是没有贡献的。很自然的想到,这些点可不可以直接跳过呢?或者说,一段没有贡献的路径,我们可不可以把它像并查集一样搞一个路径压缩呢?
因为每个点有且仅有惟一的\(fail\),所以这样做是可行的!我们设一个状态\(g[i]\),表示沿着\(i\)向上跳\(fail\)跳到的第一个能对答案产生贡献的位置(也就是某个单词结尾的\(end\)位置),找不到的话当然设为虚根\(0\)了。此状态可以通过递推得到,在求\(fail\)的时候也一起推出来了。
有了这个想法,具体推法也就很明显了。如果\(fail[i]\)为某单词结尾,那么\(g[i]=fail[i]\)。否则\(g[i]=g[fail[i]]\)。
统计答案就不跳\(fail\)了,而是跳\(g\),同样是跳到根为止。
这种优化实际复杂度并没有变(最坏情况下是一样的),但是实际效果还是挺好的,比较大的点可以优化\(25\%\)以上。
实在TLE的话, \(fread,fwrite,register,inline,\)O2套餐奉上。。。。。。
我不会告诉大家yyb_test 896ms成功冲到了rank1。。。。。。(对你没看错,是yyb_test而不是FlashHu)
附上数组版代码,可能有点丑
#include<cstdio>
#include<cstring>
#define now c[u][*p-'a']
#define skip while(*++p<=' ')//跳过空字符
const int N=1000009;
char s[N<<6],o[N<<4],*m[159];//m存每个模式串的起始位置指针
int c[N][26],f[N],e[N],g[N],q[N],a[159];//f即fail,e即end,q队列,g如上描述
int main()
{
fread(s,1,sizeof(s),stdin);//奇技淫巧之fread
register char *p=s,*p1=o;//p读入,p1输出
register int n,cnt,i,h,t,u,v,mx;
while((n=*p&15))
{
while(*++p>='0')
n*=10,n+=*p&15;
cnt=h=t=0;
//建自动机开始
for(i=1;i<=n;++i)
{
skip;m[i]=p;
for(u=0;*p>='a';++p)
u=now?now:(now=++cnt);
e[u]=i;//end存的是模式串编号而不是个数了
}
skip;m[i]=p;
//bfs开始,求fail以及g
for(i=0;i<26;++i)//第一层提前处理
if(c[0][i])q[++t]=c[0][i];
while(h<t)
{
u=q[++h];
for(i=0;i<26;++i)
if((v=c[u][i]))
{
f[q[++t]=v]=c[f[u]][i];
g[v]=e[f[v]]?f[v]:g[f[v]];
}
else c[u][i]=c[f[u]][i];//把空儿子置为fail的对应儿子,匹配的时候方便点
}
//匹配开始
for(u=0;*p>='a';++p)
for(v=u=now;v;v=g[v])//沿着g统计答案
++a[e[v]];
//统计答案开始,其实不用sort,扫一遍就好啦
mx=t=0;
for(i=1;i<=n;++i)
if(mx<a[i])mx=a[q[t=1]=i];
else if(mx==a[i])q[++t]=i;
//输出答案开始
sprintf(p1,"%d\n",mx);
while(*++p1);
for(i=1;i<=t;++i)
{
memcpy(p1,m[q[i]],m[q[i]+1]-m[q[i]]);//我也不知道为什么这里用strcpy会MLE,难道产生了缓存空间?!
p1+=m[q[i]+1]-m[q[i]];
}//记得多组数据,弄完一组全清空
memset(c,0,++cnt*104);
memset(f,0,cnt<<2);
memset(e,0,cnt<<2);
memset(g,0,cnt<<2);
memset(a,0,(n+1)<<2);
skip;
}
fwrite(o,1,p1-o,stdout);//奇技淫巧之fwrite
return 0;
}
update:
突然想到\(fail\)的形态是一棵树,那么\(g\)显然也是,那么匹配的每个点不就是对在\(g\)树上的一条链上的所有点产生\(1\)的贡献吗?何必还要暴力跳,直接把贡献暂时存起来最后再做一遍树形DP不就行了么。。。。。。
复杂度成功降至线性\(O(\sum|S|+\sum|T|)\)
#include<cstdio>
#include<cstring>
#define R register
#define now c[u][*p-'a']
#define skip while(*++p<=' ')//跳过空字符
const int N=159,S=20009,T=1000009;
char s[T<<6],o[T],*m[N];//m存每个模式串的起始位置指针
int c[S][26],f[S],e[S],g[S],q[S],a[N],he[N],ne[N],to[N];
//f即fail,e即end,q队列,g如上描述
void dp(R int x){
for(R int i=he[x];i;i=ne[i])
dp(to[i]),a[x]+=a[to[i]];
}
int main()
{
fread(s,1,sizeof(s),stdin);//奇技淫巧之fread
R char *p=s,*p1=o;//p读入,p1输出
R int n,cnt,i,h,t,pe,u,v,mx;
while((n=*p&15))
{
while(*++p>='0')
n*=10,n+=*p&15;
cnt=h=t=pe=0;
//建自动机开始
for(i=1;i<=n;++i)
{
skip;m[i]=p;
for(u=0;*p>='a';++p)
u=now?now:(now=++cnt);
at[e[u]=i]=u;//end存的是模式串编号而不是个数了
}
skip;m[i]=p;
//bfs开始,求fail以及g
for(i=0;i<26;++i)//第一层提前处理
if(c[0][i])q[++t]=c[0][i];
while(h<t)
{
if(e[u=q[++h]])
to[++pe]=e[u],ne[pe]=he[e[g[u]]],he[e[g[u]]]=pe;
for(i=0;i<26;++i)
if((v=c[u][i]))
{
f[q[++t]=v]=c[f[u]][i];
g[v]=e[f[v]]?f[v]:g[f[v]];
}
else c[u][i]=c[f[u]][i];//把空儿子置为fail的对应儿子,匹配的时候方便点
}
//匹配开始
for(u=0;*p>='a';++p)
u=now,++a[e[u]?e[u]:e[g[u]]];
//统计答案开始,其实不用sort,扫一遍就好啦
dp(0);
mx=t=0;
for(i=1;i<=n;++i)
if(mx<a[i])mx=a[q[t=1]=i];
else if(mx==a[i])q[++t]=i;
//输出答案开始
sprintf(p1,"%d\n",mx);
while(*++p1);
for(i=1;i<=t;++i)
{
memcpy(p1,m[q[i]],m[q[i]+1]-m[q[i]]);//我也不知道为什么这里用strcpy会MLE,难道产生了缓存空间?!
p1+=m[q[i]+1]-m[q[i]];
}//记得多组数据,弄完一组全清空
memset(c,0,++cnt*104);
memset(f,0,cnt<<2);
memset(e,0,cnt<<2);
memset(g,0,cnt<<2);
memset(a,0,++n<<2);
memset(he,0,n<<2);
skip;
}
fwrite(o,1,p1-o,stdout);//奇技淫巧之fwrite
return 0;
}
洛谷P3796 【模板】AC自动机(加强版)(AC自动机)的更多相关文章
- 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)
To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...
- 洛谷P1120 小木棍 [数据加强版](搜索)
洛谷P1120 小木棍 [数据加强版] 搜索+剪枝 [剪枝操作]:若某组拼接不成立,且此时 已拼接的长度为0 或 当前已拼接的长度与刚才枚举的长度之和为最终枚举的答案时,则可直接跳出循环.因为此时继续 ...
- 洛谷-P5357-【模板】AC自动机(二次加强版)
题目传送门 -------------------------------------- 过年在家无聊补一下这周做的几道AC自动机的模板题 sol:AC自动机,还是要解决跳fail边产生的重复访问,但 ...
- 洛谷-P3796-【模板】AC自动机(加强版)
题目传送门 -------------------------------------- 过年在家无聊补一下这周做的几道AC自动机的模板题 sol:AC自动机,在fail边的基础上再加一个last边, ...
- 【AC自动机】洛谷三道模板题
[题目链接] https://www.luogu.org/problem/P3808 [题意] 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过. [题解] 不再介绍基础知识了,就是裸的模 ...
- 【洛谷 P2444】 [POI2000]病毒(AC自动机)
题目链接 这么多字符串,肯定是自动机啦. 先建出AC自动机,然后怎么表示一个安全代码没有病毒代码呢? 就是存在一条路径不经过有病毒代码段结尾的节点呗. 所以呢?有环啊!dfs一下救星了. #inclu ...
- 洛谷P3375 [模板]KMP字符串匹配
To 洛谷.3375 KMP字符串匹配 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果 ...
- LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)
为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...
- 洛谷.1919.[模板]A*B Problem升级版(FFT)
题目链接:洛谷.BZOJ2179 //将乘数拆成 a0*10^n + a1*10^(n-1) + ... + a_n-1的形式 //可以发现多项式乘法就模拟了竖式乘法 所以用FFT即可 注意处理进位 ...
随机推荐
- javascript 数字字母组合的随机数
Math.random()方法用于生成,结果为0-1间的一个伪随机数(包括0,不包括1) ,通常的办法是结合parseInt().Math.floor() 或者 Math.ceil()进行四舍五入处理 ...
- ubuntu14.04上实现faster rcnn_TF的demo程序及训练过程
安装环境:Ubuntu14.04.显卡Tesla K40C+GeForce GT 705.tensorflow1.0.0.pycharm5.0 说明:原文见博客园,有问题原文下留言,不定期回复.本文作 ...
- ChineseUtils
这里获得汉字的拼音使用了pinyin4j这个插件,因为多音字的原因效果并不理想 /** * 获得汉字拼音 * @param name * @return */ @SuppressWarnings(&q ...
- IOS设备设计完整指南
作为初学者,常常不知如何下手设计,IOS应用UI设计中碰到的种种基础小问题,在此都将一一得到解答.这份完整的设计指南将带你快速上手,为IOS设计出优雅的应用吧. 关于此设计指南 此设计指南描述的是如何 ...
- 《InsideUE4》UObject(六)类型系统代码生成重构-UE4CodeGen_Private
读的不如写的快 引言 在之前的<InsideUE4>UObject(四)类型系统代码生成和<InsideUE4>UObject(五)类型系统收集章节里,我们介绍了UE4是如何根 ...
- [翻译]编写高性能 .NET 代码 第二章:垃圾回收
返回目录 第二章:垃圾回收 垃圾回收是你开发工作中要了解的最重要的事情.它是造成性能问题里最显著的原因,但只要你保持持续的关注(代码审查,监控数据)就可以很快修复这些问题.我这里说的"显著的 ...
- Yii2框架RBAC(Role-Based Access Control)的使用
1.在项目的common/config/main.php文件的components中添加如下代码: 'authManager' => [ 'class' => 'yii\rbac ...
- CodeForces - 551C 二分+贪心
题意:有n个箱子形成的堆,现在有m个学生,每个学生每一秒可以有两种操作: 1: 向右移动一格 2: 移除当前位置的一个箱子 求移除所有箱子需要的最短时间.注意:所有学生可以同时行动. 思路:二分时间, ...
- Shiro登录成功之后跳到指定URL
通常我们使用shiro,登录之后就会跳到我们上一次访问的URL,如果我们是直接访问登录页面的话,shiro就会根据我们配置的successUrl去重定向,如果我们没有配置successUrl的话,那么 ...
- JAVA在不确定具体 Annotation 类型时,获得注解参数
package com.lzw.demo; @SpringBootApplication public class DemoApplication { public static void ma ...