HDU 2222(AC自動機)

Problem Description
In the modern time, Search engine came into the life of everybody like Google, Baidu, etc.
Wiskey also wants to bring this feature to his image retrieval system.
Every image have a long description, when users type some keywords to find the image, the system will match the keywords with description of image and show the image which the most keywords be matched.
To simplify the problem, giving you a description of image, and some keywords, you should tell me how many keywords will be match.

Input
First line will contain one integer means how many cases will follow by.
Each case will contain two integers N means the number of keywords and N keywords follow. (N <= 10000)
Each keyword will only contains characters ‘a’-‘z’, and the length will be not longer than 50.
The last line is the description, and the length will be not longer than 1000000.

Output
Print how many keywords are contained in the description.

Sample Input

1
5
she
he
say
shr
her
yasherhs

Sample Output

3

題目大意:給出n個單詞,然後給出一段文本,問在文本中有多少個單詞出現過。

解題思路:這是一道AC自動機的板子題,跑一遍AC自動機的板子就可以啦。

AC自動機主要是在trie樹上加了一個fail指針匹配,如果一個單詞的某一個後綴與另一個單詞的前綴相等,那麼該位置的fail指針指向相等的這一部分。
(因爲AC自動機主要靠trie樹的思想,其實指針失配很容易就看懂了,所以建議先理解trie樹算法再來看AC自動機更好。emm ,如果可以,也可以再看一下KMP算法。)
比如該題中的樣例:
在這裏插入圖片描述
上圖就是我們得到的trie樹,箭頭表示fail指針指向(PS:因爲沒有不同顏色的筆,所以只畫了這兩個結點的fail指針的指向,其他的結點的fail指針都指向0)。我們可以看到she這個字符串和her這個字符串相比較,she的後綴等於he的前綴,因爲有了這個fail指針,如果我們搜索到she,已經匹配到e這個字符的情況下,我們也相當於匹配到he這個字符串的所有字符,而在創建這個trie樹時,he這條路徑中e字符的結點已經有值了,即出現過,那麼我們加上這個值就可以了,然後我們將her中的r加在she的後面,這樣就可以繼續匹配her這個字符串是否出現過啦。而我們得到fail樹的方法一般是使用BFS,我們先將第一層的結點加入隊列,然後再去尋找每一個結點的子節點,再加入隊列,說起來,AC自動機的原理就是這樣子啦。接下來看代碼叭。
AC代碼:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <bitset>
#include <queue>
//#include <random>
#include <time.h>
using namespace std;
#define int long long
#define ull unsigned long long
#define ls root<<1
#define rs root<<1|1
const int inf=1ll*1<<60;
const int maxn=5e5+10;
const int maxm=1e6+10;
int to[maxn][27],cnt[maxn];
char arr[maxm];
int tot,fail[maxn];
void add(char *s)//創建trie樹
{
    int now=0;
    for(int i=0;s[i]!='\0';i++){
        int x=s[i]-'a';
        if(!to[now][x]){
            to[now][x]=++tot;
        }
        now=to[now][x];
    }
    cnt[now]++;
}
void getfail()
{
    queue<int> q;
    for(int i=0;i<26;i++){
        if(to[0][i])q.push(to[0][i]);//先將第一層的結點加入隊列
    }
    while(!q.empty()){
        int x=q.front();q.pop();//取出一個結點
        for(int i=0;i<26;i++){//遍歷可能存在的結點
            if(to[x][i]){//如果存在,那麼就將它的fail指針指向他父親結點fail指針指向的結點相對應的子節點
                    //因爲父親結點已經匹配了,那麼父親結點的相對應的兒子結點就是該節點所匹配的結點。
                fail[to[x][i]]=to[fail[x]][i];
                q.push(to[x][i]);
            }
            else to[x][i]=to[fail[x]][i];//如果不存在,那麼就將父親結點fail指針指向的結點相對應的子結點放入該節點,以便匹配可能存在的另一個單詞。
        }
    }
}
int query(char *s)
{
    int now=0,ans=0;
    for(int i=0;s[i]!='\0';i++){
        int x=s[i]-'a';
        now=to[now][x];
        for(int j=now;j && cnt[j]!=-1;j=fail[j]) ans+=cnt[j],cnt[j]=-1;//在trie樹中,如果匹配到了一個結點,那麼如果該節點指向的fail指針存在,那麼這個單詞就出現過,所以遍歷所有的fail指針指向的結點
        //因爲可能會計算重複,所以加完之後,就將該結點的出現次數變爲-1,也可以減少循環次數。
    }
    return ans;
}
signed main()
{
    int T;
    cin>>T;
    while(T--){
        int n;
        scanf("%lld",&n);
        tot=0;
        memset(cnt,0,sizeof cnt);
        memset(fail,0,sizeof fail);
        memset(to,0,sizeof to);
        while(n--){
            scanf("%s",arr);
            add(arr);
        }
        getfail();
        scanf("%s",arr);
        printf("%lld\n",query(arr));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章