1. 程式人生 > >[TJOI2013]單詞 AC自動機

[TJOI2013]單詞 AC自動機

freopen 編號 sed 記錄 fail 統計單詞 str 額外 為什麽

題面:

洛谷

題解:

  很久之前做的題了,只不過之前一直90.。。。最近才發現是哪裏寫錯了。

  我們對字符集建AC自動機。

  首先考慮一個暴力的做法,把文章當做一個長串,直接在自動機上跳,但是我們會發現,這樣的復雜度可能退化到$n^2$.

  因為對於一個類似於aaaaaaaaaaaaaaaa這樣的串而言,一個點的fail總是指向它的父親,因此如果我們每次都暴力向上跳fail復雜度就不對了。

  觀察到每遍歷到一個節點,其實質就是給這個點到root的這條鏈上的每個點都+1,因此我們目標只是在fail樹上對每個點都求出子樹和。

  如果我們知道了所有標記的位置,顯然可以建樹統計一下。

  當然,也有更方便的寫法,因為一個點的編號肯定比它的fail的編號大。表現在fail樹上就是父親編號比兒子小。

  所以我們從大到小枚舉編號,把當前枚舉到的節點的權值加給父親即可。

  為什麽這樣是對的?

    因為我們可以看做是兒子先把代價給父親,在讓父親把它的代價和它兒子的代價一起向上傳。

  

  其實對於這道題而言,還有更方便的寫法,你甚至不需要建匹配串。因為文章就是由給定字符集組成的,因此我們一定會遍歷自動機上的每一個節點,所以與其再遍歷一遍,再把每個節點權值+1,不如在建自動機的時候就直接加。

技術分享圖片
 1 #include<bits/stdc++.h>
 2
using namespace std; 3 #define R register int 4 #define maxn 2500500 5 int n,ans[maxn],go[maxn],num;//num標記是第一個單詞,便於處理單詞重復的情況 6 int q[maxn],head,tail,tot;//從第一位開始存文本串 7 char s[maxn]; 8 9 struct ACtree 10 { 11 int fail[maxn], c[maxn][27]; 12 //由於是統計單詞在文章中的出現次數,相同單詞只算一次,所以val最大只能為1 13
void add()//標記是第一個單詞 14 { 15 int now=0,len=strlen(s); 16 for(R i=0;i<len;i++) 17 { 18 int v=s[i]-a; 19 if(!c[now][v]) c[now][v]=++tot; 20 now=c[now][v], ++ ans[now];//因為之後再遍歷也是直接遍歷整個樹,所以直接在這裏加 21 } 22 // if(!val[now])val[now]++;//這樣val就沒用了 23 go[num]=now;//記錄下每個單詞的結尾部分,以防遇到重復只輸出一個 24 } 25 26 void build() 27 { 28 R now; 29 for(R i=0; i<26 ;i++) 30 if(c[0][i]) q[++tail]=c[0][i];//既然fail一開始都為0,那就不用額外初始化了 31 while(head<tail) 32 { 33 now=q[++head]; 34 for(R i=0;i<26;i++) 35 if(c[now][i]) fail[c[now][i]]=c[fail[now]][i],q[++tail]=c[now][i]; 36 else c[now][i]=c[fail[now]][i];//建立虛擬節點 37 } 38 for(R i = tail; i; i --) ans[fail[q[i]]] += ans[q[i]]; 39 for(R i = 1; i <= n; i ++) printf("%d\n", ans[go[i]]); 40 } 41 42 }AC; 43 44 void pre() 45 { 46 scanf("%d",&n); 47 for(num=1; num<=n ;num++) 48 scanf("%s",s), AC.add(); 49 } 50 51 int main() 52 { 53 // freopen("in.in","r",stdin); 54 pre(); 55 AC.build(); 56 // fclose(stdin); 57 return 0; 58 }
View Code

  

[TJOI2013]單詞 AC自動機