AC自动机模板题——HDU2222

具体题目见 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;}
    }
}

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

你可能感兴趣的:(AC自动机模板题——HDU2222)