from: http://hihocoder.com/contest/hiho4/problem/1
踹圖~踹圖~~踹圖~~~
Trie圖
描述
前情回顧
上回說到,小Hi和小Ho接受到了河蟹先生偉大而光榮的任務:河蟹先生將要給與他們一篇從互聯網上收集來的文章,和一本厚厚的河蟹詞典,而他們要做的是判斷這篇文章中是否存在那些屬於河蟹詞典中的詞語。
當時,小Hi和小Ho的水平還是十分有限,他們只能夠想到:“枚舉每一個單詞,然後枚舉文章中可能的起始位置,然後進行匹配,看能否成功。”這樣非常樸素的想法,但是這樣的算法時間複雜度是相當高的,如果說詞典的詞語數量爲N,每個詞語長度爲L,文章的長度爲M,那麼需要進行的計算次數是在N*M*L這個級別的,而這個數據在河蟹先生看來是不能夠接受的。
於是河蟹先生決定先給他們個機會學習一下,於是給出了一個條件N=1,也就是說詞典裏面事實上只有一個詞語,但是希望他們能夠統計這個詞語在文章中出現的次數,這便是我們常說的模式匹配問題。而小Hi和小Ho呢,通過這一週的努力,學習鑽研了KMP算法,並在互相幫助之下,已經成功的解決掉了這個問題!
這便是Hiho一下第三週發生的事情,而現在第四周到了,小Hi和小Ho也要踏上解決真正難題的旅程了呢!
任務回顧
小Hi和小Ho是一對好朋友,出生在信息化社會的他們對編程產生了莫大的興趣,他們約定好互相幫助,在編程的學習道路上一同前進。
這一天,他們……咳咳,說遠了,且說小Ho好不容易寫完了第三週程序,卻發現自己錯過了HihoCoder上的提交日期,於是找小Hi哭訴,小Hi雖然身爲管理員,但是也不好破這個例,於是把小Ho趕去題庫交了代碼,總算是哄好了小Ho。
小Ho交完程序然後屁顛屁顛的跑回了小Hi這邊,問道:“小Hi,你說我們是不是可以去完成河蟹大大的任務了呢?”
小Hi思索半天,道:“老夫夜觀星象……啊不,我這兩天查閱了很多資料,發現這個問題其實也是很經典的問題,早在06年就有信息學奧林匹克競賽國家集訓隊的論文中詳詳細細的分析了這一問題,而他們使用的就是Trie圖這樣一種數據結構!”
“Trie圖?是不是和我們在第二週遇到的那個Trie樹有些相似呀?”小Ho問道。
“沒錯!Trie圖就是在Trie樹的基礎上發展成的一種數據結構。如果要想用一本詞典構成Trie圖的話,那麼就首先要用這本詞典構成一棵Trie樹,然後在Trie樹的基礎上添加一些邊,就能夠變成Trie圖了!”小Hi又作老師狀。
“哦!但是你說了這麼多,我都不知道Trie圖是什麼樣的呢!”小Ho無奈道。
“也是!那我們還是從頭開始,先講講怎麼用Trie樹來解決這個問題,然後在Trie樹的基礎上,討論下一步應該如何。”小Hi想了想說道。
“現在我們有了一個時間複雜度在O(ML)級別的方法,但是我們的征途在星辰大海,啊不,我們不能滿足於這樣一個60分的方法。所以呢,我們還是要貫徹我們一貫的做法,尋找在這個算法中那些冗餘的計算!“小Hi道:”那麼我們現在來看看Trie樹進行計算的時候都發生了些什麼。”
“那麼現在……”小Hi剛要開口,就被小Ho無情打斷。
“可是小Hi老師~你看在這種情況下,結點C找不到對應的後綴結點,它對應的路徑是aaabc,而aabc在Trie裏面是走不出來的!”小Ho手中揮舞着一張紙,問道。
“你個瓜娃子,老是拆老子臺做啥子!……阿不,小Ho你別擔心,我這就要講解如何求後綴結點呢~”小Hi笑容滿面的說道。
“原來如此!這樣我就知道了每一個結點的後綴結點了,接下來我就可以很輕鬆的解決河蟹先生交給我的問題了呢!”小Ho高興的說道:“但是,說好的Trie圖在哪裏呢?”
小Hi不由笑道:“你這叫買櫝還珠你知道麼?還記得我們再計算後綴結點的時候計算出的從每個點出發,經由每一個char(比如'a'..'d')會走到的結點麼?把這些邊添加到Trie樹上,就是Trie圖了!”
“原來是這樣,但是這些邊感覺除了計算後綴結點之外,沒有什麼用處呀?”小Ho又開始問問題了。
“這就是Trie圖的巧妙之處了,你想想你什麼時候需要知道一個結點的後綴結點?”小Hi實在不忍看自己的兄弟這般呆萌,只能耐着性子解釋。
小Ho頓時恍然大悟,“在這個結點不能夠繼續和文章str繼續匹配了的時候,也就是這個結點沒有“文章的下一個字符”對應的那條邊,哦!我知道了,在Trie圖中,每個結點都補全了所有的邊,所以原來需要先找到後綴結點再根據“str的下一個字符”這樣一條邊找到下一個結點,現在可以直接通過當前結點的“str的下一個字符”這樣一條邊就可以接着往下匹配了,如果本來是有這條邊的,那不用多說,而如果這條邊是根據後綴結點補全的,那便是我們想要的結果!”
“所以呢!完成這個任務的方法總的來說就是這樣,先根據字典構建一棵Trie樹,然後根據我們之前所說的構建出對應的Trie圖,然後從Trie圖的根節點開始,沿着文章str的每一個字符,走出對應的邊,直到遇到一個標記結點或者整個str都已經匹配完成了~”小Hi適時的總結道。
“而這樣的時間複雜度則在O(NL+M)級別的呢!想來是足以完成河蟹先生的要求了呢~”小Ho搬了搬手指,說道。
“是的!但是河蟹先生要求的可不是想法哦,他可是希望我們寫出程序給它呢!”
輸入
每個輸入文件有且僅有一組測試數據。
每個測試數據的第一行爲一個整數N,表示河蟹詞典的大小。
接下來的N行,每一行爲一個由小寫英文字母組成的河蟹詞語。
接下來的一行,爲一篇長度不超過M,由小寫英文字母組成的文章。
對於60%的數據,所有河蟹詞語的長度總和小於10, M<=10
對於80%的數據,所有河蟹詞語的長度總和小於10^3, M<=10^3
對於100%的數據,所有河蟹詞語的長度總和小於10^6, M<=10^6, N<=1000
輸出
對於每組測試數據,輸出一行"YES"或者"NO",表示文章中是否含有河蟹詞語。
-
6 aaabc aaac abcc ac bcd cd aaaaaaaaaaabaaadaaac
- 樣例輸出
-
YES
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=1000000+10;
struct Trie {
int mark,pre,link[26];
void init(){memset(link,-1,sizeof(link));}
} T[maxn];
int tot=1;
char a[maxn],str[maxn];
void build_trie(int len) {
int p=1;
for(int i=0; i<len; i++) {
if (T[p].link[str[i]-'a']==-1) {
T[p].link[str[i]-'a']=++tot;
T[tot].init();
}
p=T[p].link[str[i]-'a'];
if(i==len-1)T[p].mark=true;
}
}
void build_fail() {
queue<int>q;
q.push(1);
while(!q.empty()) {
int p=q.front();
q.pop();
for(int i=0; i<26; i++) {
int child=T[p].link[i];
if (child!=-1) {
int pre=T[p].pre;
while(pre!=-1)
if(T[pre].link[i]==-1)pre=T[pre].pre;
else {
T[child].pre=T[pre].link[i];
if(T[T[child].pre].mark)T[child].mark=true;
break;
}
q.push(child);
}
}
}
}
bool solve() {
int p=1;
for(int i=0; a[i]; i++)
while(true)
if(T[p].link[a[i]-'a']==-1)p=T[p].pre;
else {
p=T[p].link[a[i]-'a'];
if(T[p].mark)return true;
break;
}
return false;
}
int main() {
int n;
scanf("%d",&n);
T[1].init();
for(int i=1; i<=n; i++)scanf("%s",str),build_trie(strlen(str));
for(int i=0; i<26; i++)T[0].link[i]=1;T[0].pre=-1;//建立fail點->root
T[1].pre=0;
build_fail();
scanf("%s",a);
if(solve())puts("YES");
else puts("NO");
return 0;
}
喜歡結構化的可以看看: http://hzwer.com/5465.html