HDU 2222 Keywords Search AC自動機 Fail數組詳解

1. 題目描述

1.1. Limit

Time Limit: 2000/1000 MS (Java/Others)

Memory Limit: 131072/131072 K (Java/Others)

1.2. 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.


1.3. Input

First line will contain one integer means how many cases will follow by.

Each case will contain two integers NN means the number of keywords and NN keywords follow. (N10000)(N \le 10000)

Each keyword will only contains characters ‘a’-‘z’, and the length will be not longer than 5050.

The last line is the description, and the length will be not longer than 10000001000000.


1.4. Output

Print how many keywords are contained in the description.


1.5. Sample Input

1
5
she
he
say
shr
her
yasherhs

1.6. Sample Output

3

1.7. Source

HDU 2222 Keywords Search


2. 解讀

AC自動機 (Aho-Corasick automaton)的模板題。

AC自動機 使用了 Trie樹 的結構和 KMP算法 的思想,將所有模式串 PPTrie樹進行存儲,並且對每個節點,構造 Fail數組,這個 Fail數組KMP算法 中的 Next數組 有些類似,但不完全一樣。

Fail[x+i]=k Fail[x + i] = k

其中 xx 表示模式串 α\alphaTrie樹 中的開始位置, ii 表示我們判斷的是當前模式串分支 α\alpha 的第 ii 個節點,kk 表示 α\alpha 中以 ii 爲終點的最長後綴與 Trie樹 中的第 kk 個位置對應的模式串 β\beta 的前綴相匹配。

α[ilen:i+1]=β[0:len+1] \alpha[i-len:i + 1] = \beta[0:len + 1]

max(len) max(len)

[a:b][a:b] 爲左閉右開區間,len+1len + 1 爲後綴的長度,max(len)max(len) 表示我們要找的是最長匹配後綴。


使用以下輸入爲例

1
5
she
he
say
shr
her
yasherhs

首先構建 Trie樹 ,如圖1。每個節點左邊的數字爲節點序號,右邊爲節點存儲的字符。

Trie樹

Figure 1. Trie樹 \text{Figure 1. Trie樹}

然後通過 DFS 獲取 FailFail 數組,標號爲 1-2 的子串 sh,其後綴 h 與標號爲 4 的前綴相匹配;標號爲 1-3 的子串 she,其後綴 he 與標號爲 4-5 的前綴相匹配。

Fail[2]=4,Fail[3]=5Fail[2] = 4, Fail[3] = 5

FailFail 數組中的其他成員都指向根結點,即 Fail[xi]=0Fail[x_i] = 0

在代碼中加入輸出語句,可以看到 DFS 構建FailFail 數組的具體過程。

front: 4
char: e fail: 0
front: 1
char: a fail: 0
char: h fail: 4
front: 5
char: r fail: 0
front: 6
char: y fail: 0
front: 2
char: e fail: 5
char: r fail: 0
front: 9
front: 7
front: 3
front: 8

3. 代碼

#include <iostream>
#include <queue>
#include <string.h>
using namespace std;
const int maxN = 5e5 + 1; // 模式串長度 * 模式串數量
const int maxM = 1e6 + 1; // 目標串長度
const int chSize = 26; // 字符集大小

class AcAutomaton {
public:
    int trie[maxN][chSize];
    int vis[maxN], fail[maxN];
    int tot;
    // 初始化
    void init()
    {
        memset(vis, 0, sizeof vis);
        memset(trie, 0, sizeof trie);
        tot = 0;
    }
    // 插入
    void insert(char* str)
    {
        int len = strlen(str);
        int pos = 0;
        for (int i = 0; i < len; i++) {
            int c = str[i] - 'a';
            if (!trie[pos][c])
                trie[pos][c] = ++tot;
            pos = trie[pos][c];
        }
        vis[pos]++;
    }
    // DFS獲取Fail數組
    void build()
    {
        queue<int> q;
        // 根結點下的元素入隊
        for (int i = 0; i < chSize; i++) {
            if (trie[0][i]) {
                fail[trie[0][i]] = 0;
                q.push(trie[0][i]);
            }
        }
        // DFS
        while (!q.empty()) {
            // 獲取隊首元素
            int pos = q.front();
            // 輸出隊首元素
            // cout << "front: " << pos << endl;
            // 隊首出隊
            q.pop();
            // 遍歷隊首元素的子節點
            for (int i = 0; i < chSize; i++) {
                // 若元素ch存在
                if (trie[pos][i]) {
                    // 將fail數組的值置爲fail[隊首元素]的下一個ch對應的值
                    fail[trie[pos][i]] = trie[fail[pos]][i];
                    // 輸出構建fail數組的過程
                    // cout << "True char: " << (char)('a' + i) << " fail: " << trie[fail[pos]][i] << endl;
                    // 元素入隊
                    q.push(trie[pos][i]);
                } else {
                    // 若不存在,將trie的值置爲fail[隊首元素]的下一個ch對應的值
                    // 方便下次計算
                    trie[pos][i] = trie[fail[pos]][i];
                }
            }
        }
    }
    // 查詢
    int query(char* str)
    {
        int len = strlen(str);
        int pos = 0, ans = 0;
        for (int i = 0; i < len; i++) {
            int c = str[i] - 'a';
            pos = trie[pos][c];
            for (int j = pos; j && vis[j] != -1; j = fail[j]) {
                ans += vis[j];
                vis[j] = -1;
            }
        }
        return ans;
    }
};

AcAutomaton ac;

int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        ac.init();
        char str[maxM];
        int n;
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            scanf("%s", str);
            ac.insert(str);
        }
        ac.build();
        scanf("%s", str);
        printf("%d\n", ac.query(str));
    }
}




注:代碼參考自哈爾濱理工大學ACM-ICPC集訓隊常用代碼庫

鏈接:https://pan.baidu.com/s/1eJwyrqCDQOAlPWC1mbPRKQ

密碼:gv5k


聯繫郵箱:[email protected]

CSDN:https://me.csdn.net/qq_41729780

知乎:https://zhuanlan.zhihu.com/c_1225417532351741952

公衆號:複雜網絡與機器學習

歡迎關注/轉載,有問題歡迎通過郵箱交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章