1. 程式人生 > >AC自動機--summer-work之我連模板題都做不出

AC自動機--summer-work之我連模板題都做不出

aca cstring 指導 www. sum gets font 兩個 deb

這章對現在的我來說有點難,要是不寫點東西,三天後怕是就一無所有了。

但寫這個沒有營養的blog的目的真的不是做題或提升,只是學習學習代碼和理解一些概念。

現在對AC自動機的理解還十分淺薄,這裏先貼上目前我看過的文章:

  1. 深入理解Aho-Corasick自動機算法
  2. AC 自動機學習筆記

AC自動機相比Trie多了失配邊,結點到結點間的狀態轉移,結點到根的狀態轉移。

這裏fail的定義是:使當前字符失配時跳轉到另一段從root開始每一個字符都與當前已匹配字符段某一個後綴完全相同且長度最大的位置繼續匹配。

A - Keywords Search

題意:給多個模式串,求文本串中有多少模式串是子串。

這裏直接貼上kuangbin大佬的模板。

技術分享圖片
  1 #include <cstdio>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <cstring>
  5 #include <queue>
  6 using namespace std;
  7 struct Trie{
  8     int next[500010][26], fail[500010], end[500010];
  9     int root, L;
 10     int newnode(){
 11
for(int i=0; i<26; i++){ 12 next[L][i] = -1; 13 } 14 end[L++] = 0; 15 return L-1; 16 } 17 void init(){ 18 L = 0; 19 root = newnode(); 20 } 21 void insert(char buf[]){ 22 int len = strlen(buf); 23 int
now = root; 24 for(int i=0; i<len; i++){ 25 if(next[now][buf[i]-a] == -1){ 26 next[now][buf[i]-a] = newnode(); 27 } 28 now = next[now][buf[i]-a]; 29 } 30 end[now]++; 31 } 32 void build(){ 33 queue<int> Q; 34 fail[root] = root; 35 for(int i=0; i<26; i++){ 36 if(next[root][i] == -1){ 37 next[root][i] = root; 38 }else{ 39 fail[next[root][i]] = root; 40 Q.push(next[root][i]); 41 } 42 } 43 while( !Q.empty() ){ 44 int now = Q.front(); 45 Q.pop(); 46 for(int i=0; i<26; i++){ 47 if(next[now][i] == -1){ 48 next[now][i] = next[fail[now]][i]; 49 }else{ 50 fail[next[now][i]] = next[fail[now]][i]; 51 Q.push(next[now][i]); 52 } 53 } 54 } 55 } 56 int query(char buf[]){ 57 int len = strlen(buf); 58 int now = root; 59 int res = 0; 60 for(int i=0; i<len; i++){ 61 now = next[now][buf[i]-a]; 62 int temp = now; 63 while(temp != root){ 64 res += end[temp]; 65 end[temp] = 0; 66 temp = fail[temp]; 67 } 68 } 69 return res; 70 } 71 void debug(){ 72 for(int i=0; i<L; i++){ 73 cout << "id = " << i << " " << "fail = " << fail[i] << " " << "end = " << end[i] << endl; 74 for(int j=0; j<26; j++){ 75 cout << next[i][j] << " "; 76 } 77 cout << endl; 78 } 79 } 80 }; 81 char buf[1000010]; 82 Trie ac; 83 int main(){ 84 freopen("in.txt", "r", stdin); 85 int T; 86 scanf("%d", &T); 87 while(T--){ 88 int n; 89 scanf("%d", &n); 90 ac.init(); 91 for(int i=0; i<n; i++){ 92 scanf("%s", buf); 93 ac.insert(buf); 94 } 95 ac.build(); 96 scanf("%s", buf); 97 cout << buf << endl; 98 printf("%d\n", ac.query(buf)); 99 } 100 return 0; 101 }
View Code

C - L語言

我把學習心得放在這裏了

D - Computer Virus on Planet Pandora

題意:給一個文本串,求其正反兩個串中出現了幾個模式串。

這裏AC自動機中query()操作的作用:查詢Trie中有幾個前綴出現在文本串中。

方法是遍歷Trie樹找val=1的點即字符串最後一個字符,然後計數,同時要通過fail找回邊,這裏就是kmp的意味了。

所以才有人說AC自動機 = Trie+Kmp。

技術分享圖片
  1 #include <cstdio>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <cstring>
  5 #include <queue>
  6 using namespace std;
  7 #define mst(s, t) memset(s, 0, sizeof(s));
  8 struct Trie{
  9     int ch[250110][26], fail[250010], val[250110];
 10     int res, sz;
 11     inline void init(){ sz=1; mst(ch[0], 0); mst(val, 0); }
 12     inline int idx(char c) { return c-A; }
 13     inline void insert(char *s){
 14         int u = 0, n = strlen(s);
 15         for(int i=0; i<n; i++){
 16             int c = idx(s[i]);
 17             if(!ch[u][c]){
 18                 mst(ch[sz], 0);
 19                 ch[u][c] = sz++;
 20             }
 21             u = ch[u][c]; 
 22         }
 23         val[u] = 1;
 24     }
 25     inline void build(){
 26         queue<int> Q;
 27         int u;  fail[0] = 0;
 28         for(int i=0; i<26; i++){
 29             u = ch[0][i];
 30             if(u){ fail[u]=0;     Q.push(u); }
 31         }
 32         while( !Q.empty() ){
 33             int now = Q.front();    Q.pop();
 34             for(int i=0; i<26; i++){
 35                 u = ch[now][i];
 36                 if(!u){ ch[now][i] = ch[fail[now]][i]; continue; }
 37                 Q.push(u);
 38                 int v = fail[now];
 39                 while(v && !ch[v][i]) v=fail[v];
 40                 fail[u] = ch[v][i];
 41             }
 42         }
 43     }
 44     inline int query(char *buf, int len){ 
 45         int u = 0, res = 0;
 46         for(int i=0; i<len; i++){
 47             int c = idx(buf[i]);
 48             u = ch[u][c];
 49             int temp = u;
 50             //有幾個前綴出現在文本串中 
 51             while(temp && val[temp]!=0){
 52                 res += val[temp];
 53                 val[temp] = 0;
 54                 temp = fail[temp];
 55             }
 56         }
 57         return res;
 58     }
 59 };
 60 char tmp[5100010], buf[5100010];
 61 Trie ac;
 62 int main(){
 63 //    freopen("in.txt", "r", stdin);
 64     int T;
 65     scanf("%d", &T);
 66     while(T--){
 67         ac.init();
 68         int n;
 69         scanf("%d", &n);
 70         for(int i=0; i<n; i++){
 71             scanf("%s", buf);
 72             ac.insert(buf);
 73         }
 74         ac.build();
 75         scanf("%s", tmp);
 76         int tlen = strlen(tmp), blen=0;
 77         for(int i=0; i<tlen; i++){
 78             if(tmp[i]==[){
 79                 i++;
 80                 int cnt = 0;
 81                 while(tmp[i] >= 0 && tmp[i] <= 9){
 82                     cnt = cnt*10 + tmp[i++]-0;
 83                 }
 84                 for(int k=0; k<cnt; k++){
 85                     buf[blen++] = tmp[i];
 86                 }
 87                 i++;
 88             }else{
 89                 buf[blen++] = tmp[i];
 90             }
 91         }
 92         tmp[blen] = \0;
 93         for(int i=0; i<blen; i++){
 94             tmp[i] = buf[blen-i-1];
 95         }
 96         buf[blen] = \0;
 97         printf("%d\n", ac.query(buf, blen) + ac.query(tmp, blen));
 98     }
 99     return 0;
100 }
View Code

E - Family View

題意:找出文本串中所有模式串。

AC自動機的變化在insert()和find(),比較難想的應該是如何結合題意利用find()。

上題是計數模式串出現在文本串中的個數,而這道題則是每個模式串出現在文本串中的位置。

那麽重點就是如何標記這些位置。

這題我用的是訓練指南上的代碼風。

技術分享圖片
  1 #include <cstdio>
  2 #include <iostream>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <queue>
  6 #include <cctype>
  7 using namespace std;
  8 #define mst(s, t) memset(s, t, sizeof(s))
  9 const int maxnode = 1e6+10;
 10 const int sigma_size = 26;
 11 struct Trie{
 12     int ch[maxnode][sigma_size], val[maxnode], f[maxnode], last[maxnode], sz;
 13     int cnt[maxnode], dis[maxnode];
 14     inline void init(){ sz=1; mst(ch[0], 0); mst(val, 0); mst(cnt, 0); }
 15     inline int idx(char x){ return tolower(x)-a; }
 16     inline void insert(char *s){
 17         int u = 0, n = strlen(s);
 18         for(int i=0; i<n; i++){
 19             int c = idx(s[i]);
 20             if(!ch[u][c]){
 21                 mst(ch[sz], 0);
 22                 ch[u][c] = sz++;
 23             }
 24             u = ch[u][c];
 25         }
 26         val[u] = 1;
 27         dis[u] = n;
 28     }
 29     inline void get_fail(){
 30         queue<int> Q;
 31         int u;   f[0] = 0;
 32         for(int c=0; c<sigma_size; c++){
 33             u = ch[0][c];
 34             if(u) { last[u] = f[u]=0; Q.push(u); }
 35         }
 36         while(!Q.empty()){
 37             int now = Q.front(); Q.pop();
 38             for(int c=0; c<sigma_size; c++){
 39                 u = ch[now][c];
 40                 if(!u)  { ch[now][c]=ch[f[now]][c]; continue; }
 41                 Q.push(u);
 42                 int v = f[now];
 43                 while(v && !ch[v][c]) v = f[v];
 44                 f[u] = ch[v][c];
 45                 last[u] = val[f[u]] ? f[u] : last[f[u]];
 46                 //{(he), (she)}5  --> {(he)}2
 47                 //last[5] = val[2]? 2 : last[f[2]]
 48                 //last[5] =   1   ? 2 : 0
 49                 //last[i]:表示沿著失配指針往回走時遇到的下一個單詞結點編號 
 50             }
 51         }
 52     }
 53     inline void find(char *s){
 54         int n = strlen(s), u = 0;
 55         for(int i=0; i<n; i++){
 56             if(!isalpha(s[i]))continue;  //這裏要寫,空格等亂七八糟的東西太多 
 57             int c = idx(s[i]);
 58             u = ch[u][c];
 59             if(val[u])  prin(i, u);               //在字符串中找到前綴樹中的內容後對字符串中的信息進行標記 
 60             else if(last[u]) prin(i, last[u]);
 61         }
 62     }
 63     inline void prin(int i, int j){
 64         if(j){
 65             ++cnt[i+1];
 66             --cnt[i+1-dis[j]];   //[i+1-len, i+1)所有字符要forbid,故標記首尾 
 67             //cout << "i = " << i<< "    last["<<j<<"] = "<<last[j]<< endl;
 68             /*
 69              * 這裏last數組和fail數組回跳的方式有什麽區別 和 它是如何提高效率的,
 70              * 若有前輩知道,望直接指教,感激不盡。 
 71             */ 
 72             prin(i, last[j]);
 73         }
 74     }
 75     /*
 76     inline void show(){
 77         for(int i=0; i<sz; i++){
 78             cout<<"last["<<i<<"] = "<<last[i]<<"       f["<<i<<"] = "<<f[i]<<endl;
 79         }
 80     }
 81     */
 82 };
 83 Trie ac;
 84 char s[maxnode];
 85 int main(){
 86     freopen("in.txt", "r", stdin);
 87     int t;
 88     scanf("%d", &t);
 89     while(t--){
 90         ac.init();
 91         int n;
 92         scanf("%d", &n);
 93         while(n--){
 94             scanf("%s", s);
 95             ac.insert(s);
 96         }
 97         ac.get_fail();
 98         getchar();
 99         fgets(s, maxnode, stdin);
100         ac.find(s);
101         int ans = 0, len = strlen(s);
102         for(int i=0; i<len; i++){
103             ans += ac.cnt[i];
104             if(ans < 0)  putchar(*);
105             else putchar(s[i]);
106         }
107     }
108     return 0;    
109 }
View Code

還有就是我有一個疑惑想了很久沒有解決,也找了萬老師解惑,雖然並沒有得到很令我心服的答案,但依然很感謝萬老師的指導。

訓練指南245頁有關last指針的分析,他說後綴鏈接last[j]是結點j沿著失配指針往回走。我的疑惑是:這個last指針和fail有什麽區別,或是二者回跳方式有何不同。如果有哪位聚聚或老師對這個問題有過思考請不吝指教,或指導下學習方式等,本菜雞定感激不盡。

AC自動機--summer-work之我連模板題都做不出