1. 程式人生 > 其它 >P5357【模板】AC自動機(二次加強版)

P5357【模板】AC自動機(二次加強版)

題目傳送門

一、題解分析

因為是什麼二次加強版,所以大家先去做一下加強版吧,做法差不多。

沒做過的看上面的連結,有一些變數名可能會在剛才那一篇\(blog\)出現過,所以建議大家再去過一下。

好了,看到這裡大家都一定做過加強版了吧,那麼這道題的做法也是差不多的:我們這一次不需要求出現最多的字串啦,直接將\(cnt\)陣列輸出就好了!(應該都知道\(cnt\)陣列是什麼吧,就是統計每個模式串在文字串出現多少次的陣列)

但重複的單詞有沒有影響啊!有啊!對於加強版這一次重複的單詞就會有影響啦,怎麼辦?

這道題有相同字串要統計,設當前字串是第\(i\)個,我們用一個\(Map[i]\)陣列(不是\(STL\)

那個)存((當前字串在\(Trie\)中的那個位置)的\(flag\)),最後把\(cnt[Map[i]]\)輸出就\(OK\)了。另外\(flag\)只在第一次賦值時變化,其他都不變。

二、樸素版本

//本程式碼手動吸氧,可以得76分
#include <bits/stdc++.h>
using namespace std;
const int N = 2 * 1e5 + 10;
const int M = 2 * 1e6 + 10; //最大長度 2*10^6,比加強版大了一倍
char s[N], T[M];            //模式串與文字串
/*
每個模式串Si在T中出現的次數
資料不保證任意兩個模式串不相同。

理解:
1、如果能保證模式串不重複,那麼就是加強版。
2、由於模式串可能重複,所以需要進行一下處理:採用一個類似於並查集的思想:
因為模式串可能有重複的,而我們在本題中不關心第幾個模式串出現幾次,只關心“長著一樣的字串,出現了幾次”
比如:第2、3兩個序號輸入的模式串都是 abcd,其實,我們關注的是 abcd出現的次數,而不是 2和3出現的次數。
因為,採用的辦法就是:一樣的字串,第一次錄入記錄它是family的族長,後面再輸入一樣的字串,都使用前面的族長編號做為自己家族的標識
*/
int n;              //模式串數量
int tr[N][26], idx; // Trie樹
int id[N];          // 節點號-mapping->模式串
int ne[N];          // AC自動機的失配陣列 fail指標
int ans[N];         //記錄答案,每個模式串被命中幾次
int family[N];      //每個輸入的模式串x,都隸屬於某一個family[x],黃海稱之為家庭,類似於並查集?

void insert(char *s, int x) {
    int p = 0;
    for (int i = 0; s[i]; i++) {
        int t = s[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++idx;
        p = tr[p][t];
    }
    if (!id[p]) id[p] = x; //當字串重複時,保留第一個錄入的序號
    family[x] = id[p];     // 記錄族長資訊
}

int q[N];
void bfs() {
    int hh = 0, tt = -1; //將佇列的頭和尾變數寫在這裡,可以有效防止多組測試資料的初始化問題
    for (int i = 0; i < 26; i++)
        if (tr[0][i]) q[++tt] = tr[0][i];
    while (hh <= tt) {
        int p = q[hh++];
        for (int i = 0; i < 26; i++) {
            int t = tr[p][i]; // p狀態,通過i這條邊,到達的新狀態t; 也可以理解為是字首
            if (!t)
                tr[p][i] = tr[ne[p]][i]; //節點 指向父節點失配指標的i這條邊
            else {
                ne[t] = tr[ne[p]][i]; //失配指標指向父節點失配指標的i這條邊
                q[++tt] = t;          //存在的要入佇列
            }
        }
    }
}

//查詢字串s在AC自動機中出現的次數
void query(char *s) {
    int p = 0;                   //從root出發
    for (int i = 0; s[i]; i++) { //列舉文字串每個字元
        int j = s[i] - 'a';      // j為當前字元的對映數字
        //每走到一個位置,需要進行檢查
        int k = tr[p][j];            // k為拷貝出來的臨時變數,準備遊標去一路向上尋找
        while (k) {                  //如果還沒有到達root節點
            if (id[k]) ans[id[k]]++; //如果k這個節點是某個模式串的終點,此模式串號 id[k] 對應的命中個數+1
            k = ne[k];               //繼續向上遊動
        }
        p = tr[p][j]; //走Trie樹路徑
    }
}

int main() {
    scanf("%d", &n);
    //由模式串構建Trie樹
    for (int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(s, i);
    }
    //構建AC自動機
    bfs();

    //輸入文字串
    scanf("%s", T);
    //查詢
    query(T);
    //對於每一個模式串,輸出它代表的字串在文字串中出現的次數
    for (int i = 1; i <= n; i++) printf("%d\n", ans[family[i]]);
    return 0;
}

三、拓撲序優化遞推版本

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;  //模式串長度
const int M = 2000010; //文字串長度
int n;                 //模式串個數
char s[N], T[M];       //模式串,文字串

// Trie樹
int tr[N][26], idx, id[N];
void insert(int x) {
    int p = 0;
    for (int i = 0, p = 0; s[i]; i++) {
        int t = s[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++idx;
        p = tr[p][t];
    }
    id[x] = p;
}

//構建AC自動機
int q[N], ne[N];
void bfs() {
    int hh = 0, tt = -1;
    for (int i = 0; i < 26; i++)
        if (tr[0][i]) q[++tt] = tr[0][i];
    while (hh <= tt) {
        int t = q[hh++];
        for (int i = 0; i < 26; ++i) {
            if (tr[t][i]) {
                ne[tr[t][i]] = tr[ne[t]][i];
                q[++tt] = tr[t][i];
            } else
                tr[t][i] = tr[ne[t]][i];
        }
    }
}

int f[N];
void query(char *s) {
    int p = 0;
    for (int i = 0; s[i]; i++) { //列舉文字串每一個字元
        int t = s[i] - 'a';      //字元對映的數字t,可以理解為邊
        p = tr[p][t];            //走進去,到達的位置替換p
        f[p]++;                  //標識此位置有人走過,記錄走的次數
    }
    // Trie樹中的節點個數=root(1個)+有效字元數(idx)
    //從大到小列舉,是因為這是符合拓撲序的,倒著來可以形成遞推的效果
    for (int i = idx; i; i--) f[ne[q[i]]] += f[q[i]]; //一路向上,計算疊加值
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(i);
    }
    //構建AC自動機
    bfs();
    //文字串
    scanf("%s", T);
    query(T);
    //輸出
    for (int i = 1; i <= n; i++) printf("%d\n", f[id[i]]);
    return 0;
}