AC自动机

标签: ac 自动机 | 发表时间:2011-10-19 19:03 | 作者:Mato_No1 Sosi
出处:http://www.cppblog.com/MatoNo1/
AC自动机就是在Trie树上加入一些失败指针(fail,类似KMP中的next),使得它在某个结点失配的时候能够转移到该结点失败指针指向的结点继续匹配,从而实现多串匹配(单主串多子串)。

【1】Trie树的结构:
首先是结点类型:
struct node {
    
int mul, ch[SZ], fail;
} T[MAXN];
其中SZ是字符集的大小,比如小写字母集SZ=26,数字集SZ=10等。
另外这个mul表示的是该结点的重复次数(和平衡树中的比较像),就是这个结点所对应的字符串(从根到该结点路径上的所有边上的字符组成的字符串)出现了几次。
(不过这种表示法MS不是很好……如果有卡空间的,比如结点总数和SZ之积达到了100M以上,而真正的边又很少的时候……就囧掉了……而如果用指针的话在Linux下面又会CE掉……各位神犇来说一下怎么解决啊囧……)
然后,Trie树的0号结点(T[0])是空结点(用来代替指针中的NULL),因此真正结点下标从1开始。1号结点(T[1])一般都作为根结点,所以下面直接写#define root 1。
还有(这句话是除草用的……)Trie树的字符全部都在边上而不在点上,因此T[x].ch[c]其实指的是“结点x引出的字符为c的边所指向的结点”,简称结点x的c子结点。

【2】自动机的建立:
首先要建立Trie树(也就是在空的Trie树上插入所有要匹配的子串)。这个随便搞一下就傻掉了,因此直接上代码:
void ins()
{
    
int len = s0.length(), x = root, c;
    re(i, len) {
        c 
= s0[i] - 97;
        
if (!T[x].ch[c]) {T[x].ch[c] = ++N; T[N].mul = 0; re(j, SZ) T[N].ch[j] = 0;}
        x 
= T[x].ch[c];
    }
    T[x].mul
++;
}
这里string s0是待插入的字符串,定义成全局变量,因为在参数中出现string有可能爆掉。

然后就是建立自动机了。
这一过程其实是用BFS遍历来实现的。首先,T[root].fail=0(root也是整棵树中唯一的fail为0的结点)并将root入队。下面按照BFS的顺序依次取出队所有的点,对于结点i,若它存在k子结点j(也就是j=T[i].ch[k],且j≠0),则结点j入队,并计算j的失败指针fail,方法:从T[i].fail(不是i)开始不断上溯,直到找到一个存在k子结点的结点或者到root都木有找到(结点下标为0,即NULL)。若找到了一个存在k子结点的结点x,则将T[j].fail置为T[x].ch[k],否则(直到root都木有找到),T[j].fail=root。

到这里失败指针的用处就显现了:如果匹配到结点x的时候失配(即接下来的一个字符是c而T[x].ch[c]=0),可以立刻转到T[x].fail进行匹配,因为T[x].fail有以下三个特征:
<1>其深度严格小于x的深度;
<2>其代表的字符串一定是x代表的字符串的后缀;
<3>该结点一定是满足条件<1><2>的所有结点中深度最小的结点;

代码:
void mkf()
{
    Q[
0= root; T[root].fail = 0;
    
int i, j, x;
    
for (int front=0, rear=0; front<=rear; front++) {
        i 
= Q[front];
        re(k, SZ) 
if (j = T[i].ch[k]) {
            x 
= T[i].fail;
            
while (x && !T[x].ch[k]) x = T[x].fail;
            
if (x) T[j].fail = T[x].ch[k]; else T[j].fail = root;
            Q[
++rear] = j;
        }
    }
}

【3】匹配过程:
在有了失败指针时,其匹配过程就和KMP差不多了。
设主串为A(代码中同),依次扫描A[0..A.length()-1]进行匹配。设目前匹配到了第i位,A[i]=c,当前结点为x。匹配过程中将进行以下操作:
<1>成功匹配(T[x]有c子结点),则进入T[x]的c子结点;
<2>失配(T[x]无c子结点),则从T[x].fail开始,沿着失败指针上溯,直到找到一个有c子结点的结点为止。如果到了root都木有找到这样的结点,则停止在root处;
<3>设立结点y,从当前的结点x开始不断上溯到root(注意root也要算,因为子串中可能有空串),将中间所有结点的mul值累加进最终结果(表示这些字符串在主串中出现了,统计次数),如果题目中要求统计不重复的子串个数(如HDU2222),则在累加之后将它们全部置为0,防止下次再次累加。这一步操作实质上就是统计A中所有以A[i]为右端点的子串个数。

这样,整个过程就傻掉了。

代码:
void solve()
{
    
int len = A.length(), x = root, y, c; res = 0;
    re(i, len) {
        c 
= A[i] - 97;
        
while (x && !T[x].ch[c]) x = T[x].fail;
        
if (!x) x = root; else x = T[x].ch[c];
        y 
= x;
        
while (y) {res += T[y].mul; T[y].mul = 0; y = T[y].fail;}
    }
}

有关例题:
【1】HDU2222
裸的多串匹配问题,模板题;
有关该题的详细资料(包括易疵点)见这里
【2】HDU2896
比2222稍难一些,但还是模板题。注意这题的子串木有重复的,因此mul可以设为bool。
【3】HDU3065
统计每个子串出现的次数(可以重复),也是模板题。
以上三题均不卡空间。


Mato_No1 2011-10-19 19:03 发表评论

相关 [ac 自动机] 推荐:

AC自动机

- Sosi - C++博客-Mato is No.1
AC自动机就是在Trie树上加入一些失败指针(fail,类似KMP中的next),使得它在某个结点失配的时候能够转移到该结点失败指针指向的结点继续匹配,从而实现多串匹配(单主串多子串). 其中SZ是字符集的大小,比如小写字母集SZ=26,数字集SZ=10等. 另外这个mul表示的是该结点的重复次数(和平衡树中的比较像),就是这个结点所对应的字符串(从根到该结点路径上的所有边上的字符组成的字符串)出现了几次.

KMP、AC自动机在字符串匹配类动态规划问题中的应用

- Sosi - C++博客-Mato is No.1
有一类动态规划(其中也包含递推)问题,要求满足一些限制条件的字符串,这些限制条件是“需要含有某个子串”或“不能含有某个子串”,那么KMP、AC自动机等就有大用了. 题意:字符集中有一些字符,给出每个字符的出现概率(它们的和保证为1),再给出一个子串B,求:任给一个长度为N的字符串A(只能包含字符集中的字符),使得S是A的子串的概率.

AC-Comparatives发布2011年度总结报告,卡巴斯基夺冠

- - 奶味网-IT人- 最新RSS订阅
著名评测机构AV-Comparatives发布了它的2011年度总结报告. 在报告中,时隔5年,卡巴斯基终于再次夺冠,获得2011年度产品称号. 从左到右分别为手动潜在有害程序检测测试(二月)、启发式智能检测测试(二月)、性能测试(套装)、全功能动态保护测试(part 1)、手动潜在有害程序检测测试(八月)、启发式智能检测测试(八月)、性能测试(AV部分)、病毒清除测试、全功能动态保护测试(part 2).

Levenshtein 自动机(拼音纠错)

- -
原文: http://blog.jobbole.com/80659/. 在上一期的超酷算法中,我们聊到了BK树,这是一种非常聪明的索引结构,能够在搜索过程中进行模糊匹配,它基于编辑距离 (Levenshtein distance),或者任何其它服从三角不等式的度量标准. 今天,我将继续介绍另一种方法,它能够在常规索引中进行模糊匹配搜索,我们将它称之为 Levenshtein自动机.

遥控玩具 Wall-E 改装为声控、有眼睛的半自动机器人,更加可爱俏皮

- kebo - Engadget 中国版
千万不要被这个凸出来的眼睛吓倒喔. 其实你现在盯着的是来至加拿大的 DJ Sures 的杰作:他为遥控玩具 Wall-E (瓦力)安装了一个镜头,然后再用 EZ-B Robot Controller 的软件来加入颜色、动作、语音命令和人脸办识功能. 仔细看看,你也会留意到这台用 AA 电池的机器人也配置了几个电动马达,不仅让头部跟随人脸或者小球来移动,也可以叫它挥手跳舞呢(跳转后有影片).