【专辑】AC自动机
http://www.cs.uku.fi/~kilpelai/BSA05/lectures/slides04.pdf
http://www.docin.com/p-46845432.html(上边原文地址如果无法访问的话可以访问这一个~)
这是我找到的AC自动机最好的资料
感觉网上其他一些资料都没能阐述的很好,只是草草的介绍一下fail指针,画几张草图而已,贴一下不是很搞笑甚至还带有错误的模板,真正精髓的地方都很模糊的一笔带过
这片资料虽然是英文的,阅读起来也许有一点点小障碍,而且不像其他一样长篇大论,只是精短的几句描述,但看完之后会有“大彻大悟”的感觉~好了,自己去欣赏这篇极品论文吧..
下边给出几道题目以及解题思路供大家练习
模板题:
Keywords Search
网络流上流传最广的AC自动机模板题,问你目标串中出现了几个模式串
如果一个结点是单词末尾的话out标记为true,在search的时候对于每个结点都向fail指针找,找到out为true的就将其标记为false,且ans++
病毒侵袭
问你目标串中出现了几个模式串
同上一题,只是这题要输出模式串的ID,且字符是所有可见字符,要开[128]的儿子结点,还好没有太卡内存
病毒侵袭持续中
目标串中各个模式串出现了几次
这就更简单了,都不用把out标记成false了~
Detect the Virus
解码之后就和模板题没什么差别
AC自动机+矩阵
DNA Sequence
问你长度为N的串中不包含了模式串的串有几个
n属于1 ~ 2000000000看到这个数据范围我们就应该敏感的想到这是矩阵~
最多100个结点,先建好所有结点(不包括模式串结尾的和fail指向结尾的结点,所以其实最多只有90个有效结点)之间的转化关系,然后二分矩阵乘法,复杂度O(100^3*log(2000000000))
考 研路茫茫——单词情结
问你长度为1~N的串中包含了模式串的串总共有几个
上题的加强版,先要把总数26^1 + 26^2 + … + 26^N算出来,然后减去所有不包含的…反正比上题恶心一点点
答案要模2^64,直接用unsinged __int64 就OK了
AC自动机+DP
Censored!
大数+简单DP
Wireless Password
位压缩,每个节点有2^10次状态,找到至少K个
Ring
每个单词有val,关键需要输出路径,比较挺麻烦的,需要倒过来,如果暴力点就直接用string吧。。
DNA REPAIR
就是不要走到尾结点上,如果和匹配串不相同的话+1dp
Searching the String
找可以重叠和不能重叠的串个有多少,记录每个单词最后出现的pos,注意有重复的串
Lost’s revenge
RE的神题,非常卡内存。。。空间复杂度(500*11^4)(这还是缩水后的,原来的是(2500*11^4)),时间复杂度要再乘上转化复杂度(4),如果状态变化的系数高一点就过不了。。表示要非常优化的代码才能过
空罐 Cans
高中生题,做了一个小时,关键是debug一个白痴错误de了半个小时。惭愧啊。。不过这题比较好玩的是罐子基因分裂后第一个字符会消失,需要用fail指针来转移状态了
fail用来转移变短的状态,chd来转移变长的基因,同时还要记录基因的长度(长度l转移的时候需分三类讨论,l = 1,l 恰为根结点到当前结点的距离,l 大于该距离),DP状态是100*1500[长度,结点]用滚动数组
Resource Archiver
乳鸽的神题,状态很容易看出来,有50000*1024,很难保持,我用散列表超时了,用bitset刚好可以卡过,不过后来我想,只有尾结点才有效,中间的很多结点完全可以忽略,可以先用最短路吧各个尾结点之间的距离算出来,经过测试,不到50个点,马上就优化到50*1024了,本来9s多过的优化到了100多MS
最后献上我的模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #define FF(i,a) for( int i = 0 ; i < a ; i ++ ) #define CC(m,what) memset(m,what,sizeof(m)) const int NODE = 1500; const int CH = 4; int chd[NODE][CH] , sz;//结点个数 int word[NODE];//关键数组,记录每个单词尾结点的信息,每道题目都不一样 int fail[NODE];//传说中的失败指针 int Que[NODE];//辅助队列 int sw[128];//string swap每个字符对应的Index,方便模板化 void Ins(char *a, int val) { int p = 0; for(; *a ; a ++) { int c = sw[*a]; if(!chd[p][c]) { CC(chd[sz] , 0); word[sz] = 0; chd[p][c] = sz ++; } p = chd[p][c]; } word[p] = val; } void AC() { int *s = Que , *e = Que; FF(i,CH) if(chd[0][i]) { fail[ chd[0][i] ] = 0; *e++ = chd[0][i]; } while(s != e) { int p = *s++; FF(i,CH) { if(chd[p][i]) { int v = chd[p][i]; *e++ = v; fail[v] = chd[fail[p]][i]; //对word[v]按word[fail[v]]里的内容进行处理 } else { chd[p][i] = chd[fail[p]][i]; } } } } //AC()函数处理后 chd[p][i] 就是在p结点进行i转移到达的结点 int main() { fail[0] = 0; FF(i,26) sw[i+'a'] = i; //下面两句每次都必须初始化 CC(chd[0],0); sz = 1; } |
Popularity: 53% [?]
[...] 题目来源:http://www.notonlysuccess.com/index.php/aho-corasick-automaton/ [...]
在效率上,说错了,今天在CF上一道题目同样的方法,看你的代码A了,我队友用结构体超时,然后就把结构体改成了数组就A了,很郁闷。。。
[回复]
存储树的时候,开数组和开结构体的区别是不是很大啊,在数组上
[回复]
shǎ崽 回复:
五月 4th, 2012 at 18:40
什么区别?效率的话交道题目就知道了
[回复]
Aho-Corasick Automaton …这个post的permantent link拼错了呵呵。
[回复]
shǎ崽 回复:
三月 28th, 2012 at 14:45
permantent是什么东东啊。。。?哪里拼错了,看了半天没看出来。。。
[回复]
ddmbr 回复:
三月 28th, 2012 at 19:30
http://www.notonlysuccess.com/index.php/aho-corasick-automation 。。偶然看到,这个链接啦。wordpress是这么叫的。。
[回复]
shǎ崽 回复:
三月 28th, 2012 at 19:42
soga,原来是链接名字写错了,看的真仔细哈~:)
[回复]
很叼.
[回复]
大牛的模板严格的说是是trie图吧,写的真的很好
能否给一个完整的HDU2222的模板呢?
可以的话发到邮箱中Acorder@163.com
谢了
[回复]
在底下看到了查找的模板 学习中
[回复]
怎么模版里没有查找函数?
另外Out指针是干什么用的?
[回复]
请问Searching the String这题怎么DP啊?求指导~谢啦~
[回复]
[...] 被糊成一片的题,不过总算做完notonlysuccess大牛的自动机专题了。 先将资源串和病毒串建成一个trie图。在为资源建树的时候注意区分出一个是另一个子串的情况,剔除出去。在建立失败指针的时候就留下病毒的标记就行了,资源的标记用不到了。 图建好之后就要找两个资源的末尾的最短路了,表示从一个的末尾最少经过多少个字符到达另一个末尾。看到题解上说此处用Dijkstra找,但感觉对于这种没有权值的还是用BFS更好。BFS时遇到病毒就知道自己不能走那条路了,遇不到向下找就行了。直到找到或无路可走。 找完两资源间的最短路再用动归算出长度最小的链即可。就是以资源被用到的情况与此前用到的最后一个资源作为状态转移就行了。 [...]
[...] 顺着notonlysuccess大牛的自动机做到了这一个题。吐下槽:顺着别人的分类做有个不好的地方是很容易就看到了剧透,于是我就知道了此题一个关键就是记录每个单词最后出现的pos。不过做完之后发现这个根本不是自动机啊,只用到了trie树。 [...]
[...] 在此特别感谢:shǎ崽神牛,并附以链接:http://www.notonlysuccess.com/?p=607 [...]
[...] 在此特别感谢:shǎ崽神牛,并附以链接:http://www.notonlysuccess.com/?p=607 [...]
[...] http://www.notonlysuccess.com/?p=607 [...]
膜拜! 受教了 !!
[回复]
[...] 附带HH博客,【专辑】AC自动机:http://www.notonlysuccess.com/?p=607 [...]
仔细拜读了大牛的模板,果然高端啊。字句凝结了智慧的结晶。感谢大牛放出模板。rp++。
[回复]
如果能发到我的邮箱那就更谢谢了。
[回复]
神牛,给个ac自动机的模板吧。线段树我也是在你这学的,写得太好了
[回复]
请问大牛,Lost’s revenge那题,的空间复杂度
(2500*11^4))是hash的复杂度吗?这题能不能直接做?
[回复]
问一下,您在query的时候有什么加速策略吗?不就是和KMP一样的嘛?
为啥我的HDU2222要跑500ms
还有,这个题有重复单词吧,您单纯记录true和false行吗?
[回复]
shǎ崽 回复:
八月 14th, 2010 at 11:51
我记得没有重复单词的吧。。
统计访问fial指针的时候当发现已经访问过了,已经是true了,那就不用再遍历下去了~
[回复]
昵称(必须) 回复:
八月 14th, 2010 at 19:34
我试了呀。有重复单词呀。。。您看我的query到底差在哪了?老是优化不到您的100+ms
void query(char *b ){
int q = 0;
for( ; *b ; b++){
int c = sw[ *b ];
while(q > 0 && chd[q][c] == 0 )
{
q = fail[q];
}
q=chd[q][c];
int temp=q;
while( temp>0 ){
ans+=word[temp];
word[temp]=0;
temp=fail[temp];
}
}
}
[回复]
shǎ崽 回复:
八月 14th, 2010 at 20:34
int work(char *a) {
int ret = 0 , p = 0;
for(int c = sw[*a] ; *a ; a ++ , c = sw[*a]) {
p = chd[p][c];
int pp = p;
while(pp && isword[pp] != -1) {
ret += isword[pp];
isword[pp] = -1;
pp = fail[pp];
}
}
return ret;
}
你没有用我的模板吧?
我的模板直接把所有儿子都建好了,有些是虚拟儿子.直接访问就好了~
还有已经访问过的点不是要标记掉的么,下次就不用访问了..
看看我的查找函数吧
[回复]
昵称(必须) 回复:
八月 14th, 2010 at 21:35
昂。。我明白您的意思了。谢谢啊!!
但是这样可以处理这种问题吗?
2
sheet
ee
主串:sheet
结果应该是2吧?
但您那样查找结果会是1吧
[回复]
wythend 回复:
九月 3rd, 2011 at 08:37
加个out 指针就行了,out指针 沿着失败指针指向第一个单词。
先用fail指针和goto函数遍历,
s,sh,she,shee,sheet的标记数都为1.
sheet的Out指针指向根。
shee的out指针指向ee,ee的标记数=ee标记数+shee的标记数。
she的Out指针指向根…….
答案就是所有的单词的标记数的和。
[回复]
Orthocenter 回复:
十二月 11th, 2011 at 13:15
不会吧 有沿着失败指针走了
[回复]
Wythend 回复:
九月 3rd, 2011 at 08:32
QUERY 依赖一个 Out 指针,这能把QUERY 复杂度降低在O(N)内。
out指针就是沿着失败指针走,走到的第一个 为答案(是个单词)的指针。
一开始,先用goto指针(走向下一个匹配字母)和fail指针 遍历trie树,经过的点的标记 数+1.
最后,从最底层开始往上做。假如 sherry 的fail 指针指向 herry,再指向erry,但是它的Out指针指向 erry,那erry的标记数=erry原来的表奇数+sherry的标记数。
然后sherry沿着失败指针走向herry,这时,herry的out指针也指向erry,所以erry的标记数=erry的标记数+herry的标记数。
[回复]
“考研路茫茫——单词情结”那题超时了。。有什么好的办法吗?
[回复]
shǎ崽 回复:
八月 8th, 2010 at 19:13
使用矩阵的话应该不会超时吧?
这题时间复杂度都用在矩阵乘法上边
[回复]
zyue1105 回复:
八月 8th, 2010 at 20:43
想到问题了~原来输入的L可能是2^31 – 1,我做矩阵幂的时候加了个1,正好溢出。。
[回复]
请教一下Ring那题,我想知道你是怎么记录答案的,还有是如何保证字典序的,谢谢!
[回复]
shǎ崽 回复:
八月 6th, 2010 at 12:22
我直接用string比较的,不一个个比较字典序
[回复]
st 回复:
八月 7th, 2010 at 20:54
是把所有权值最优的都记录下来然后比较吗?
[回复]
shǎ崽 回复:
八月 8th, 2010 at 19:13
是的,所以说我的是暴利解法
[回复]
这个模板不仅仅是根据上面的资料写的吧,fail指针的求法已经改进过了
[回复]
shǎ崽 回复:
八月 5th, 2010 at 11:54
嗯嗯`~改进过了的~
[回复]
cans那题三种情况:
l = 1,l 恰为根结点到当前结点的距离,l 大于该距离
请教后面那两种情况有什么不同呢?
[回复]
shǎ崽 回复:
七月 17th, 2010 at 19:22
我dp记录的最后一个基因所在的位子,如果l大于”根结点到当前结点的距离”,那么去掉最前面的一个基因的话还是最后这个基因的位子还是不变的,所以有状态转移方程如下:
if(l == 1) {
add(ans1 , dp[S][l][i]);
} else if(l <= dis[i]){
add(dp[T][l-1][fail[i]] , dp[S][l][i]);
} else {
add(dp[T][l-1][i] , dp[S][l][i]);
}
[回复]
inowfordream 回复:
七月 17th, 2010 at 22:36
感谢hh神…终于想明白了!
[回复]
shǎ崽 回复:
七月 18th, 2010 at 16:40
呵呵,这题比上边的都好玩多了~
[回复]
空罐 Cans
高中生题,做了一个小时,关键是debug一个白痴错误de了半个小时。惭愧啊。。不过这题比较好玩的是罐子基因分裂后第一个字符会消失,需要用fail指针来转移状态了
fail用来转移变短的状态,chd来转移变长的基因,同时还要记录基因的长度(长度l转移的时候需分三类讨论,l = 1,l 恰为根结点到当前结点的距离,l 大于该距离),DP状态是100*1500[长度,结点]用滚动数组
l 恰为根结点到当前结点的距离,l 大于该距离
这两个有什么不同呢?不都是在fail结点上长度加1么?
[回复]
好犀利的题
[回复]
水题一道:
http://acm.hnu.cn/online/?action=problem&type=show&id=10104
[回复]
目标串中各个模式串出现了几次
这就更简单了,都不用把out标记成false了~
这样做貌似复杂度会退化成O(nk),就像做n次kmp一样
[回复]
请问大牛,Keywrod Search那题,您说的“在search的时候对于每个结点都向fail指针找”,是指每次搜索的时候都要这样找??
[回复]
shǎ崽 回复:
三月 24th, 2010 at 18:29
是的,不过可以标记一下,如果找过了的就标记为-1下次如果还遇到-1就不用再找了
[回复]
这句话错了一个字方面模板化,应该是方便模板化。
[回复]
shǎ崽 回复:
三月 22nd, 2010 at 20:54
呵呵~
[回复]
大牛的网站很漂亮,但是从我的firefox上看,褐色的背景,黑色的字。。。看起来很辛苦啊……
[回复]
shǎ崽 回复:
三月 19th, 2010 at 11:50
恩?有吗?我也是一直用firefox浏览器的呀
[回复]
shǎ崽 回复:
三月 19th, 2010 at 11:52
可能网速有点卡吧,红色的背景没能显示出来~
[回复]
我那个题完全是个意外= =
时限放10s都不会显得多。。。
[回复]
shǎ崽 回复:
三月 16th, 2010 at 12:57
膜拜阿姨,我没优化过的程序跑了9.8s
[回复]
一起来就看见这个好东西,刚好在学这个,多谢了
[回复]
shǎ崽 回复:
三月 16th, 2010 at 08:04
呵呵,感谢捧场
[回复]
还有一道你没搜到吧。。。是海峡两岸OI比赛的题目Cans
http://zerojudge.tw/ShowProblem?problemid=b179
hh神牛去秒了吧
[回复]
shǎ崽 回复:
三月 16th, 2010 at 00:19
感谢梦翅牛供题~多亏你这题,我发现我代码非常容易出错
很多东西全部写得挤在了一起,查找字符的地方因为一个小错误检查了半个小时。。
需要改写模板~~
[回复]