日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

AC自动机学习笔记

發(fā)布時間:2023/12/24 windows 25 coder
生活随笔 收集整理的這篇文章主要介紹了 AC自动机学习笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

什么是自動機

一般指確定有限狀態(tài)自動機,所以AC自動機不是自動AC機

自動機是一個非常廣泛使用的數學模型

  • 自動機是一個對信號序列進行判定的模型

    解釋一下上面那句話

    信號序列是指一串有順序的信號例如字符串的從前到后每一個字符

    判定是指對某一個命題給出真或者假的判斷

    對于自動機,一共存在3種信號序列

    1. 不能識別

    2. 判定結果為真

    3. 判定結果為假

  • 自動機的結構其實是一張有向圖

    其中自動機每個節(jié)點都是一個判定節(jié)點,節(jié)點只是狀態(tài)而非任務,邊可以接受多種字符

    下面的是一個判斷一個二進制數是不是偶數的自動機

    從起始結點開始,從高位到低位接受這個數的二進制序列,看最終停在哪里。

    若最終停在紅圈結點,則是偶數,否則則反之

  • 自動機只是數學模型,不是算法,不是數據結構

    因此用不同的實現方法可以得到不同的復雜度

形式化定義

一個\(\text{DFA}\)(確定有限狀態(tài)自動機,即自動機)由五部分組成

  • 字符集\(\sum\) :

    本自動機只能輸入這些字符

  • 狀態(tài)集合\(Q\) :

    如果把一個\(\text{DFA}\)看成有向圖則\(\text{DFA}\)中的狀態(tài)就相當于圖上的頂點

  • 起始狀態(tài)\(s\) :

    對于\(s\in Q\)\(s\)是一個特殊的狀態(tài)

  • 接受狀態(tài)集合\(F\) :

    對于\(F\subseteq Q\),\(F\)是一組特殊狀態(tài)

  • 轉移函數\(\delta\) :

    \(\delta\) 是一個接受兩個參數返回一個值的函數,其中第一個參數和返回值都是一個狀態(tài)而第二個參數是字符集\(\sum\)中的一個字符

    把一個\(\text{DFA}\)看成一張有向圖,\(\text{DFA}\)中的\(\delta\)就相當于邊,每條邊上都有一個字符

\(\text{DFA}\)的作用是識別字符串,對于自動機 \(\text A\) ,若它能識別字符串 \(S\) ,那么 \(A(S)=\mathrm{T}\) ,反之\(A(S)=\mathrm{F}\)

\(DFA\) 讀入一個字符串時,從初始狀態(tài)起按照轉移函數一個一個字符地轉移。

如果讀入完一個字符串的所有字符后位于一個接受狀態(tài),那么稱 \(DFA\) 接受 這個字符串,反之稱 \(DFA\) 不接受 這個字符串。

如狀態(tài) \(v\) 沒有字符 \(c\) 的轉移,則令 \(\delta(v,c)=\mathrm{null}\) ,而 \(\mathrm{null}\) 只能轉移到 \(\mathrm{null}\) ,且 \(\mathrm{null}\) 不屬于接受狀態(tài)集合。

無法轉移到接受狀態(tài)的狀態(tài)可以視作 \(\mathrm{null}\) ,也可以說 \(\mathrm{null}\) 代指所有無法轉移到接受狀態(tài)的狀態(tài)

我們擴展定義轉移函數 [\delta] ,令其第二個參數可以接收一個字符串: [\delta(v,s)=\delta(\delta(v,s[1]),s[2..|s|])] ,擴展后的轉移函數就可以表示從一個狀態(tài)起接收一個字符串后轉移到的狀態(tài)。那么, [A(s)=[\delta(start,s)\in F]] 。

下圖是一個接受且僅接受字符串 "\(a\)", "\(ab\)", "\(aac\)" 的 \(\text{DFA}\)

\(\text{AC}\)自動機

AC自動機是以Trie為基礎結合KMP思想建立的自動機

KMP算法是求單字符串對單字符串的匹配使用的,而AC自動機是求多個字符串在一個字符串上的匹配使用的

AC自動機的實現

AC自動機需要提前知道所有的需要匹配的字符串

  • 第一步 把需要匹配的字符串構建成一棵Trie樹

  • 第二步 對Trie樹上的所有節(jié)點構造失配指針

構建Trie樹

普通的Trie,該怎么建就怎么建

這里借用大佬的圖片

構建失配指針

\(fail\)指針在這里的作用是每次沿著Trie樹匹配,當前位置沒有匹配上時,直接跳轉到失配指針所指向的位置繼續(xù)進行匹配

在這里\(fail\)指針指向的是一個在\(\text{Trie}\)里存在的最長的與真后綴相同的字符串。

OI-wiki的圖舉個例子

\(she\),她的真后綴有 \(he\)\(e\)\(\varnothing\),其中 \(he\)\(\varnothing\) 存在于 \(\text{Trie}\) 樹中,則讓 \(9\) 號節(jié)點的 \(fail\) 指針指向最長的 \(he\) 的末尾節(jié)點 \(2\) 號節(jié)點

\(her\),她的真后綴有 \(er\)\(r\)\(\varnothing\),只有 \(\varnothing\) 存在于 Trie 樹中,則讓 \(3\) 號節(jié)點的 \(fail\) 指針指向根節(jié)點 \(0\)

如何求出失配指針

當前節(jié)點 \(p\) 代表的字符是 \(c\)\(p\)\(fail\) 指針應指向 \(p\) 的父親的 \(fail\) 指針的代表 \(c\) 的兒子

如圖,\(9\) 代表的字符是 \(e\)\(9\) 的父親是 \(8\)\(8\)\(fail\) 指針指向 \(1\)\(1\) 的代表 \(e\) 的兒子是 \(2\),因此 \(9\)\(fail\) 指針指向 \(2\) 號節(jié)點。

如果\(p\)不存在代表\(c\)的兒子則讓\(c\)\(fail\)指針指向\(p\)\(fail\)指針指向的節(jié)點\(p'\)的代表\(c\)的兒子

如OI-wiki上的圖

這里是OI-wiki上的完整動圖

  • 藍色結點:BFS 遍歷到的結點 u
  • 藍色的邊:當前結點下,AC 自動機修改字典樹結構連出的邊。
  • 黑色的邊:AC 自動機修改字典樹結構連出的邊。
  • 紅色的邊:當前結點求出的 fail 指針
  • 黃色的邊:fail 指針
  • 灰色的邊:字典樹的邊

我們可以以此來寫出代碼

$My\ Code$
#include<bits/stdc++.h>
using namespace std;
namespace IO{
    inline void close(){std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);}
    inline void Fire(){freopen(".in","r",stdin);freopen(".out","w",stdout);}
    inline int read(){int s = 0,w = 1;char ch = getchar();while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}return s*w;}
    inline void write(int x){char F[200];int tmp=x>0?x:-x,cnt=0;;if(x<0)putchar('-') ;while(tmp>0){F[cnt++]=tmp%10+'0';tmp/=10;}if(cnt==0)putchar('0');while(cnt>0)putchar(F[--cnt]);putchar(' ');}
}
using namespace IO;
class AC{
public:
    class Trie{
    public:
        int fail,vis[26],end; 
    }Tr[1000000];
    int cnt; 
    inline void clear(){
        memset(Tr,0,sizeof(Tr));
    }
    inline void ins(string s){
        int l=s.length(),q=0;
        for(int i=0;i<l;++i){
            if(!Tr[q].vis[s[i]-'a']) Tr[q].vis[s[i]-'a']=++cnt;
            q=Tr[q].vis[s[i]-'a']; 
        }
        Tr[q].end+=1;
    }
    inline void Get(){
        queue<int>Q; 
        for(int i=0;i<26;++i){
            if(Tr[0].vis[i]!=0){
                Tr[Tr[0].vis[i]].fail=0;
                Q.push(Tr[0].vis[i]);
            }
        }
        while(!Q.empty()){
            int u=Q.front();
            Q.pop();
            for(int i=0;i<26;++i){
                if(Tr[u].vis[i]!=0){
                    Tr[Tr[u].vis[i]].fail=Tr[Tr[u].fail].vis[i];
                    Q.push(Tr[u].vis[i]);
                }
                else
                    Tr[u].vis[i]=Tr[Tr[u].fail].vis[i];
            }
        }
    }
    inline int Ask(string s){
        int l=s.length(),q=0,ans=0;
        for(int i=0;i<l;++i){
            q=Tr[q].vis[s[i]-'a'];
            for(int t=q;t&&Tr[t].end!=-1;t=Tr[t].fail){
                ans+=Tr[t].end;
                Tr[t].end=-1;
            } 
        }
        return ans;
    }
}AC;
signed main(){
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    string s;
    int m=read();
    while(m--){
        int n=read();
        AC.clear();
        for(int i=1;i<=n;++i){
            cin>>s;
            AC.ins(s);
        }
        AC.Get();cin>>s;
        write(AC.Ask(s));
        puts("");
    }
}


洛谷的完整題面

【模板】AC 自動機(簡單版)

題目描述

給定 \(n\) 個模式串 \(s_i\) 和一個文本串 \(t\),求有多少個不同的模式串在文本串里出現過。
兩個模式串不同當且僅當他們編號不同。

輸入格式

第一行是一個整數,表示模式串的個數 \(n\)
\(2\) 到第 \((n + 1)\) 行,每行一個字符串,第 \((i + 1)\) 行的字符串表示編號為 \(i\) 的模式串 \(s_i\)
最后一行是一個字符串,表示文本串 \(t\)

輸出格式

輸出一行一個整數表示答案。

樣例 #1

樣例輸入 #1

3
a
aa
aa
aaa

樣例輸出 #1

3

樣例 #2

樣例輸入 #2

4
a
ab
ac
abc
abcd

樣例輸出 #2

3

樣例 #3

樣例輸入 #3

2
a
aa
aa

樣例輸出 #3

2

提示

樣例 1 解釋

\(s_2\)\(s_3\) 編號(下標)不同,因此各自對答案產生了一次貢獻。

樣例 2 解釋

\(s_1\)\(s_2\)\(s_4\) 都在串 abcd 里出現過。

數據規(guī)模與約定

  • 對于 \(50\%\) 的數據,保證 \(n = 1\)
  • 對于 \(100\%\) 的數據,保證 \(1 \leq n \leq 10^6\)\(1 \leq |t| \leq 10^6\)\(1 \leq \sum\limits_{i = 1}^n |s_i| \leq 10^6\)\(s_i, t\) 中僅包含小寫字母。

例題

Keywords Search

AC自動機板子題,注意每組數據都需要對AC自動機進行\(clear\)操作

點擊查看代碼
#include<bits/stdc++.h>
using namespace std;
namespace IO{
    inline void close(){std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);}
    inline void Fire(){freopen(".in","r",stdin);freopen(".out","w",stdout);}
    inline int read(){int s = 0,w = 1;char ch = getchar();while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}return s*w;}
    inline void write(int x){char F[200];int tmp=x>0?x:-x,cnt=0;;if(x<0)putchar('-') ;while(tmp>0){F[cnt++]=tmp%10+'0';tmp/=10;}if(cnt==0)putchar('0');while(cnt>0)putchar(F[--cnt]);putchar(' ');}
}
using namespace IO;
class AC{
public:
    class Trie{
    public:
        int fail,vis[26],end; 
    }Tr[1000000];
    int cnt; 
    inline void clear(){
        memset(Tr,0,sizeof(Tr));
    }
    inline void ins(string s){
        int l=s.length(),q=0;
        for(int i=0;i<l;++i){
            if(!Tr[q].vis[s[i]-'a']) Tr[q].vis[s[i]-'a']=++cnt;
            q=Tr[q].vis[s[i]-'a']; 
        }
        Tr[q].end+=1;
    }
    inline void Get(){
        queue<int>Q; 
        for(int i=0;i<26;++i){
            if(Tr[0].vis[i]!=0){
                Tr[Tr[0].vis[i]].fail=0;
                Q.push(Tr[0].vis[i]);
            }
        }
        while(!Q.empty()){
            int u=Q.front();
            Q.pop();
            for(int i=0;i<26;++i){
                if(Tr[u].vis[i]!=0){
                    Tr[Tr[u].vis[i]].fail=Tr[Tr[u].fail].vis[i];
                    Q.push(Tr[u].vis[i]);
                }
                else
                    Tr[u].vis[i]=Tr[Tr[u].fail].vis[i];
            }
        }
    }
    inline int Ask(string s){
        int l=s.length(),q=0,ans=0;
        for(int i=0;i<l;++i){
            q=Tr[q].vis[s[i]-'a'];
            for(int t=q;t&&Tr[t].end!=-1;t=Tr[t].fail){
                ans+=Tr[t].end;
                Tr[t].end=-1;
            } 
        }
        return ans;
    }
}AC;
signed main(){
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    string s;
    int m=read();
    while(m--){
        int n=read();
        AC.clear();
        for(int i=1;i<=n;++i){
            cin>>s;
            AC.ins(s);
        }
        AC.Get();cin>>s;
        write(AC.Ask(s));
        puts("");
    }
}

下面是花絮

總結

以上是生活随笔為你收集整理的AC自动机学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。