1. 程式人生 > >AC自動機(KMP+字典樹)

AC自動機(KMP+字典樹)

AC自動機(KMP+字典樹)
題目:輸入N個串,判斷有多少個搜尋串的子串
In				out
1				4
7
a
ab
abc
abcd
abcde
abcdef
abcdefg
abcd
#include<bits/stdc++.h>
using namespace std;
char str[1000000+100];
struct node{
    int count;					//當前結點有多少個字串共同享用
    struct node *next[26];		//指向26個字母的指標
    struct node *fail;			//失配指標
    void init(){				//建構函式
        for(int i = 0; i < 26; i++) next[i] = NULL;
        count = 0;
        fail = NULL;
    }
} *root;
void insert(){
    int len, k;							//開始建樹
    node *p = root;					//先把指標指向根
    len = strlen(str);					//求出串長
    for(k = 0; k < len; k++){				//遍歷一次字串
        int pos = str[k] - 'a';				//讀出當前字元在字母表中的位置
        if( p->next[pos] == NULL ){		//如果為空則開闢新結點,初始化,P指向往下指
            p->next[pos] = new node;
            p->next[pos]->init();
            p = p->next[pos];
        }
        else							//即使憶開闢,也要往下指
            p = p->next[pos];
    }
    p->count++;						//當前結點使用次數加一,注意是最後字元的結點才加!!
}
void getfail()
{
   int i;
   node *p = root, *son, *temp;				//指向根的指標,兒子指標,臨時指標
   queue <struct node *> que;				//廣搜
   que.push(p);							//把根壓入佇列
   while( !que.empty() ){					//只要佇列不為空就就迴圈
       temp = que.front();					//讀出最前面的結點
       que.pop();							//從佇列刪去
       for(i = 0; i < 26; i++){
           son = temp->next[i];					//遍歷一次全部兒子,每次記為SON
           if(son != NULL){						//存在這個兒子
               if(temp == root) {son->fail = root;}	//temp是根則son失配時必指向根
               else{
                   p = temp->fail;				//讀出temp的失配指標
                   while( p ) {					//除了根肯定不為空
                       if(p->next[i]){			//如果temp失配指標結點有一樣的兒子
                           son->fail=p->next[i]; //則son指向那個兒子
                           break;
                       }
                       p=p->fail;				//如果沒有這個兒子就遞迴失配!
                   }
                   if(!p)  son->fail=root;	//廣搜下失配指標為空的只有根故son指向根
               }
               que.push(son);//把指向這個兒子的指標壓入佇列
           }
       }
   }
}
void query()
{
    int len, i, cnt = 0;
    len = strlen(str);
    node *p, *temp;
    p = root;
    for( i = 0; i < len; i++)
    {
        int pos = str[i]-'a';
        while( !p->next[pos]&&p!=root )//因為根是沒有失配指標的
		p = p->fail;//遞迴失配
        p = p->next[pos];//訪問兒子
        if( !p ) p=root;//沒有這個兒子就回到根開始
        temp = p;//儲存當前掃到的結點P,用TEMP來計算
        while( temp!=root )
        {
            if(temp->count >= 0)
            {
                cnt += temp->count;
                temp->count = -1;
            }
            else break;
            temp = temp->fail;//當前結點的所有失配點的數量都會被計算,因為本題是求有幾個串是模板串的子串
        }
    }
    printf("%d\n",cnt);
}
int main()
{
    int cas,n;						//案例數
    scanf("%d",&cas);
    while(cas--)
    {
        root=new node;
        root->init();
        root->fail=NULL;
        scanf("%d",&n);			//一共有多少個子串
        int i;
        getchar();					//吸收回車符
        for(i=0;i<n;i++)
        {
            gets(str);				//輸入N個子串並插入字典樹,str全域性變數不用傳參
            insert();
        }
        getfail();					//計算失配指標
        gets(str);					//輸入搜尋串
        query();					//詢問搜尋串出出現,子串的次數
    }
    return 0;
}