AC自动机模板题——HDU2222

Posted on 2011-10-19 19:47 Mato_No1 阅读(937) 评论(0)  编辑 收藏 引用 所属分类: 字符串匹配
具体题目见HDU2222,其实就是一个裸的多串匹配的问题(给出一个主串和N个子串,求出几个子串在主串中出现过)。

我真是太沙茶了……这么水的题目调了N久,找了N位神犇帮我看代码,最终才找出来BUG……

易疵点:
(1)本题的子串是可以相同的,此时Trie的每个结点要设一个mul值,表示该结点对应的字符串在所有子串中重复的次数,另外,不要为了省空间把mul定义成char型,有可能所有的字符串全相同,因此需要定义成int(事实证明不会爆空间),这是本沙茶被折磨了这么久的主要原因
(2)Trie采用静态存储,0号结点作为空结点(NULL),因此真正的结点编号从1开始,另外root一般都是1号结点;
(3)注意在建立自动机以及匹配的时候,所有要沿fail上溯的地方,其边界都是0(NULL,注意不是root)或者找到一个有对应子结点的结点。注意到0还没有找到的处理方法:在建立自动机的时候,将T[j]置为root;在匹配的时候,将x置为root;

代码(模板)(那些标了Attention的地方都是易疵的):
#include <iostream>
#include 
<stdio.h>
#include 
<string>
using namespace std;
using std::string;
#define re(i, n) for (int i=0; i<n; i++)
#define root 1
const int MAXN = 500001, MAXLEN = 1000001, SZ = 26, INF = ~0U >> 2;
struct node {
    
int mul, ch[SZ], fail;    //Attention
} T[MAXN];
int N, Q[MAXN], res;
string s0, A;
char tmp[MAXLEN], tmp0[51];
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
++;
}
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;        //Attention
            if (x) T[j].fail = T[x].ch[k]; else T[j].fail = root;    //Attention
            Q[++rear] = j;
        }
    }
}
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;    //Attention
        if (!x) x = root; else x = T[x].ch[c];    //Attention
        y = x;
        
while (y) {res += T[y].mul; T[y].mul = 0; y = T[y].fail;}      //Attention
    }
}
int main()
{
    
int tests, n;
    scanf(
"%d"&tests);
    re(testno, tests) {
        N 
= 1; T[root].mul = 0; re(i, SZ) T[root].ch[i] = 0;
        scanf(
"%d"&n); getchar();
        re(i, n) {
            gets(tmp0);
            s0 
= tmp0;
            ins();
        }
        gets(tmp);
        A 
= tmp;
        mkf();
        solve();
        printf(
"%d\n", res);
    }
    
return 0;
}

【2011年10月19日】今天发现了匹配过程中的一个可优化的地方:对于一个点x以及它的所有返回结点(这里把所有沿着x的失败指针不断上溯直到root路径上的结点都称为返回结点),由于不可重复计数,可以将它们的mul值置为原来mul值的相反数(-mul),而不是0,表示该结点已经统计过。这样在下一次y的上溯过程中一旦发现一个mul值为负的点就不用继续上溯了,因为上面的点一定也已经统计过了。
当然,这仅限于单主串,如果是多主串则需要在每次匹配之前把Trie树中所有结点的mul值(如果是负数的的话)全部重新取反。为了节省时间,可以在匹配过程中把所有统计过的(mul值改为负数的)结点全部放进一个辅助的队列里,然后取反时只要处理队列中的结点就行了。

加入该优化后的代码(solve部分):
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 && T[y].mul >= 0) {res += T[y].mul; T[y].mul = -T[y].mul; y = T[y].fail;}
    }
}

下面是优化的实测结果(第一个为优化后的,第二个为优化前的),可以看出,该优化的力度很大。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理