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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

hiho一下 第四周 Hihocoder #1036 : Trie图

發布時間:2024/9/30 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hiho一下 第四周 Hihocoder #1036 : Trie图 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

#1036 : Trie圖

時間限制:20000ms 單點時限:1000ms 內存限制:512MB

描述

前情回顧

上回說到,小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想了想說道。

“還記得我們在第二周時,是如何使用Trie樹解決字符串自動補全問題的么?”小Hi如是問道。

“還記得,就是對于每一個詢問,根據其每個位置上的字符,在Trie樹上走出對應的邊!”小Ho的記憶力還是挺不錯的,很快便答了上來。

小Hi滿意的點了點頭,繼續問道:“那你想想怎么用Trie樹來解決河蟹先生交代的任務?”

“好的!”小Ho滿口答應,隨即分析道:“現在的這個問題和第二周遇到的問題的不同之處在于,第二周時一定是從詢問的第一個字符開始匹配,然后找出所有可能的匹配,而我們現在遇到的問題是可以從詢問的任意一個位置開始匹配,看是否會在Trie樹上走到一個標記結點(標記結點對應路徑為一個屬于詞典的單詞)。

“沒錯,那你準備怎么做呢?”

“我準備對于螃蟹先生給我的文章,還是像之前我們相出的樸素算法那樣,枚舉一個起始位置,然后我們的問題就變成了:是否從這個起始位置開始的一段字符(也就是從這個起始位置開始的字符串的一個前綴字符串),它存在于“河蟹”詞典里面 ?而這個問題,就和第二周的問題幾乎一樣了,唯一不同的是,我是要一直在Trie樹中走下去直到無邊可走,或者走到一個標記結點的時候才能夠停下來,前者代表沒有任何需要河蟹的單詞,后者則說明我們找到了。”小Ho井井有條的分析道。

“也就是說,第二周我們成功解決了計算前綴匹配的數量這樣一個問題,而這一周的任務卻是可以在任意位置匹配,所以我們就枚舉一個起始點,將這個問題轉化成前綴匹配這樣一個我們已知的問題來做,這樣的思路么?”小Hi總結道。

“嗯!我就是這么想的~”小Ho道。

“嗯,這個方法聽起來挺有意思的,而且仔細分析一下,這樣做所需要的計算次數會在M*L這個數量級上,比我們之前的樸素算法已經好了很多呢~”小Hi夸獎了一番。

“嘿嘿,但是你之前說的Trie圖是怎么回事,它又能將計算次數縮減到怎樣的數量級呢?”小Ho的好奇心也是燃燒了起來。

“且聽我說~”

“現在我們有了一個時間復雜度在O(ML)級別的方法,但是我們的征途在星辰大海,啊不,我們不能滿足于這樣一個60分的方法。所以呢,我們還是要貫徹我們一貫的做法,尋找在這個算法中那些冗余的計算!“小Hi道:”那么我們現在來看看Trie樹進行計算的時候都發生了些什么。”

“你看這組輸入——文章str、詞典dic還有我們構建的Trie樹tree,我們在算法過程中,先枚舉第一個字符作為起始位置,并最多匹配到第k個字符,因為str[1..k]這一段在tree中對應的結點A結點沒有str[k+1]這一條邊。這時候我們便要枚舉第二個字符作為起始位置,并最多匹配到第k2個字符,這同樣是因為str[2..k2]這一段在tree中對應的結點B結點沒有str[k2+1]這一條邊。也就是說我們在最開始的計算中,要先從tree的0號結點走到A結點,然后回到0號結點,再走到B結點。”小Hi在黑板上畫了一些奇奇怪怪的符號,對小Ho如是解說道。


“是的!等等,我怎么覺得這里似曾相識呢?”小Ho奇道。

“問得好~那么你覺不覺得這個過程和上一周的KMP算法很相似,都是枚舉原串(文章、str)的起始位置,然后在模式串(Trie樹)中依次進行匹配?”小Hi說道。

“是的!不同之處就在于模式串就是在一個數組里一個個匹配下來,而Trie樹則是在一個樹結構中一個個順著邊走~這無非就是單個詞語和多個詞語的差別了是么?”小Ho也是一點就透。

“沒錯!那我們再回想一下我們當時是怎么優化KMP的——我們既然已經從str的當前起點i開始匹配了l個長度,那么在枚舉str的下一個起點i+1的時候,就意味著最開始的l-1個字符都已經在之前的計算中匹配過了,如果我們能夠利用好這個信息的話,就能夠大大的減少時間復雜度。

“換句話說,如果我們從str的當前起點開始,匹配了l個長度走到了A結點,如果我們把A結點對應的字符串(即從tree的0號走到A結點的路徑)去掉第一個字符,形成一個新的字符串,那么這個字符串肯定是和從str的下一個起點開始,長度為l-1的子串是一樣的,而如果我們能夠預先找到這個字符串在tree中對應的結點B',我們就不用像之前所說的那樣從0號節點走到A結點然后回到0號結點再走到B結點,而是可以直接從0號結點走到A結點然后直接跳轉到B’結點然后再根據從str[i+l..k1]這一段走到B結點!”小Hi一口氣說道,頓時感覺口干舌燥,于是拿起了一旁的杯子,猛灌了一口涼開水。

”哦!那么如果用之前的這個例子的話,從str的第一個位置開始,匹配了3個字符走到了A結點,對應的字符串是abc,如果第一個字符a去掉變成bc,這個字符和從str的第二個位置開始長度為2的字串bc的確是一樣的,此時bc在tree中對應的結點是B'結點,所以我們用之前的算法的話就是從0號結點走到A結點,然后再從0號結點走到B結點,現在可以直接從A結點走到B‘結點,然后根據str的第4(i+l=1+3)個字符走到B結點!”小Ho趁著小Hi休息的功夫,也是拿起了之前小Hi給出的例子推演道。”

“沒錯!所以我們的問題規約成了:如何對于一棵給定的Trie樹,找到其中每一個結點對應的后綴結點——這個結點在Trie中對應路徑去掉第一個字符之后在Trie中對應的結點。“小Hi擦了把汗,感覺舒爽許多,于是繼續說道。

“我大致懂了!這個后綴結點就和我們在KMP算法中求解的NEXT數組是一個意思!”小Ho開心道。

“你真聰明~”小Hi夸獎道。

“那么現在……”小Hi剛要開口,就被小Ho無情打斷。

“可是小Hi老師~你看在這種情況下,結點C找不到對應的后綴結點,它對應的路徑是aaabc,而aabc在Trie里面是走不出來的!”小Ho手中揮舞著一張紙,問道。


“你個瓜娃子,老是拆老子臺做啥子!……阿不,小Ho你別擔心,我這就要講解如何求后綴結點呢~”小Hi笑容滿面的說道。

“先看之前你說的那個例子,如果tree中存在一個結點D,其對應的路徑是aabc,那么這個結點的后綴結點是哪一個?”小Hi問道。


“aabc……去掉第一個字符就是abc,對應的是A結點,所以D結點的后綴結點是A!”小Ho很快便做出了回答。

“那么問題不就簡單了么,既然結點D是不存在的,那么不就意味著這個開始結點的枚舉,是肯定在中途就要找不到實際上是沒有意義的么,直接從C結點跳轉到A結點就可以了!所以只需要令C結點的后綴結點是A結點,像D結點這種不存在的結點當然要視為冗余計算,扔掉就行了!”小Hi老師斬釘截鐵道。

“D結點好可憐……但是,如果從tree的根節點到D結點的路徑中有標記結點怎么辦?這樣的跳過會不會導致標記結點被忽略掉了?”小Ho問道。


“如果不注意的話是會的呢!這就要引進一個新的概念,后綴結點為標記結點的結點也需要被標記,比如像對應路徑為aab的E結點就是標記點對么?而aaab對應的F結點的后綴結點便是E結點,所以需要對F結點進行標記,這樣在走到F結點的時候,就知道已經匹配出了一個河蟹詞語了呢。”小Hi耐心答道。


“那么接下來就開始說怎么快速有效的求后綴結點!小Ho,你先回答我:樹結構最大的特點是什么?”小Hi問道。

“是遞歸結構!”小Ho想也沒想就回答道。

“真聰明!雖然是導演安排好的臺詞,但是回答速度真是一流呢!”小Hi點了點頭,繼續說道:“所以我們想要求Trie樹種每個結點的后綴結點,最直觀的方法也就是像當初我們求解KMP的NEXT數組時那種從左到右的拓撲順序一樣,從根節點開始,以寬度優先遍歷的順序,依次求解每一個結點的后綴結點。

“嗯!這樣可以保證每個結點對應的后綴結點,由于其對應字符串長度一定至少少1,所以一定會在它之前得到計算?但是這樣有什么用呢?誒,我想到一個,這樣就可以知道它的后綴結點是不是標記結點了,從而決定自己是不是要被標記是么?”小Ho決定打破砂鍋問到底。

“別急!聽我慢慢說來。”小Hi不知從哪摸出一把羽扇,扇了兩下,問道:“你看這棵Trie樹,根節點的后綴結點是哪個?”


”根節點對應的字符串是空,去掉第一個字符……還是空,所以就是根節點自己了是吧?”小Ho想了想,說道。

“是的,那你看從根節點連出去的這三個點n1,n2,n3他們的后綴結點是哪個?”小Hi繼續問道。

“他們對應的字符串都只有一個字符,所以去掉一個字符就變成空了,于是他們的后綴結點也都是根節點。”小Ho也繼續答道。

“那么現在,假設所有深度小于B結點的結點的后綴結點都已經算出來了,我想要算B結點的后綴結點,有沒有什么好的方法呢?”小Hi隨手填了幾個結點的后綴結點,向小Ho問道。

“如果考慮遞歸的思路的話,B結點的父親結點是對應字符串為bc的B'結點,B'結點的后綴結點是n3結點,所以從B'結點出發經'd'這樣一條邊到達的結點B的后綴結點自然應該就是從B'結點的后綴結點n3出發經'd'這樣一條邊到達的結點——G結點了!”小Ho仔細研究了下,答道。”這么說來,是不是所有結點都可以這么求呀,如果它父親結點是通過編號為char的一條邊走向它的,那么只要找到它父親的后綴結點,并且走出編號為char的一條邊,就能夠找到它的后綴結點了?”

“差不多就是這個思路呢!但是你有沒有想過如果它父親結點的后綴結點并沒有編號為char的一條邊,你該怎么辦?”小Hi也是不厭其煩,繼續問道。

“我想想,比如說結點G的父親結點I的后綴結點J沒有'c'這樣一條邊,但是結點J的后綴結點n1卻有'c'這樣一條邊,由于后綴結點每次都是去掉前幾個字符,所以后綴結點的后綴結點也相當于是“弱”一點的后綴結點,在沒有更好的選擇的情況下(因為這是第一次找到的有'c'這樣一條邊的后綴結點),G的后綴結點就應該是結點K了吧!”小Ho仔細想想,答道。

“你這樣的話,會不會覺得,每次都要往回不停的找后綴結點,挺浪費時間的呢?”這下子換到小Hi打破砂鍋問到底了。

“那該怎么辦?”小Ho也是沒轍了。

“你看看這么做怎么樣,我還是按照寬度優先搜索的順序遍歷整棵樹,對于每一個結點,我不僅僅要求出它的后綴結點,我還要求出到達這個點后,經由每一個char(比如'a'..'d')會走到的結點。由于到達這個結點之后,所有深度比它小的結點的這些值都算出來了,于是我可以直接通過父親節點的后綴結點經由“父親節點走到當前結點經過的邊”走到的結點來計算我的后綴結點,同時這個后綴結點所要計算的值也都計算出來了,所以我可以通過這個后綴結點經由每一個char(比如'a'..'d')會走到的結點來計算我經由每一個char(比如'a'..'d')會走到的結點。”小Hi大致的說了一下思路。

“小Hi老師,我聽暈了!”小Ho報告說。

“這個簡單,我就拿這個例子給你依次算一算。”


“如果用trie(X)表示X的根節點,next(X)('a')表示從X出發標號為'a'的邊指向的結點,我們可以知道trie(0)=0, next(0)('a')=1, next(0)('b')=2, next(0)('c')=3, next(0)('d')=0。”


“由于trie(1)=0, 我們可以補上從1出發的'a','d'這兩條邊:next(1)('a')=next(0)('a')=1, next(1)('d')=next(0)('d')=0”

“由于trie(2)=0, 我們可以補上從2出發的'a','b','d'這三條邊:next(2)('a')=next(0)('a')=1, next(2)('b')=next(0)('b')=2, next(2)('d')=next(0)('d')=0”

“由于trie(3)=0, 我們可以補上從3出發的'a','b','c'這三條邊:next(3)('a')=next(0)('a')=1, next(3)('b')=next(0)('b')=2, next(3)('c')=next(0)('c')=3”


“由于trie(4)=next(trie(1))('b')=2, 我們可以補上從4出發的'a','b','d'這三條邊:next(4)('a')=next(2)('a')=1, next(4)('b')=next(2)('b')=2, next(4)('d')=next(2)('d')=0”

“由于trie(5)=next(trie(1))('c')=3, 我們可以補上從5出發的'a','b','c','d'這四條邊:next(5)('a')=next(3)('a')=1, next(5)('b')=next(3)('b')=2, next(5)('c')=next(3)('c')=3, next(5)('d')=next(3)('d')=7”

“由于trie(6)=next(trie(2))('c')=3, 我們可以補上從6出發的'a','b','c'這三條邊:next(6)('a')=next(3)('a')=1, next(6)('b')=next(3)('b')=2, next(6)('c')=next(3)('c')=3”

“由于trie(7)=next(trie(3))('d')=0, 我們可以補上從7出發的'a','b','c','d'這四條邊:next(7)('a')=next(0)('a')=1, next(7)('b')=next(0)('b')=2, next(7)('c')=next(0)('c')=3, next(7)('d')=next(0)('d')=0”

“由于trie(8)=next(trie(4))('c')=6, 我們可以補上從8出發的'a','b','c','d'這四條邊:next(8)('a')=next(6)('a')=1, next(8)('b')=next(6)('b')=2, next(8)('c')=next(6)('c')=3, next(8)('d')=next(6)('d')=9”

“由于trie(9)=next(trie(6))('d')=7, 我們可以補上從9出發的'a','b','c','d'這四條邊:next(9)('a')=next(7)('a')=1, next(9)('b')=next(7)('b')=2, next(9)('c')=next(7)('c')=3, next(9)('d')=next(7)('d')=0”

“此時這個圖已經變得過于復雜了,我就不畫出來了,但是我想你已經可以從我上面所說的知道每個節點的后綴結點了呢!”小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<cstdio> #include<cmath> #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<map> #include<queue> #include<set> #pragma comment(linker, "/STACK:102400000,102400000") using namespace std;class node{ public:node(){bad=false;prev=NULL;for(int i=0;i<26;i++)next[i]=NULL;}bool bad;//是否是終點node *next[26];node *prev;//前綴節點 };class trie{ public:trie(){root=new node();}void in(string &str){node *proot=root;int l=str.size();for(int i=0;i<l;i++){if(!proot->next[str.at(i)-'a'])proot->next[str.at(i)-'a']=new node();proot=proot->next[str.at(i)-'a'];}proot->bad=true;//單詞結尾節點}void bulid (){queue<node*>Q;node *proot=root;for(int i=0;i<26;i++){if(proot->next[i]){proot->next[i]->prev=root;Q.push(proot->next[i]);}}while(!Q.empty()){proot=Q.front();Q.pop();for(int i=0;i<26;i++){node *p=proot->next[i];if(p&&!p->bad){node* prev=proot->prev;while(prev){if(prev->next[i]){p->prev=prev->next[i];if(p->prev->bad)p->bad=true;break;}elseprev=prev->prev;}if(p->prev==NULL)p->prev=root;Q.push(p);}}}}bool sea(string &str){int l=str.size();node *proot=root;for(int i=0;i<l;i++){while(true){if(proot->next[str.at(i)-'a']){proot=proot->next[str.at(i)-'a'];if(proot->bad)return true;break;}elseproot=proot->prev;if(proot==root||!proot){proot=root;break;}}}return false;} private:node *root; };int main(){ios::sync_with_stdio(false) ;int n;trie T;string word;string pas;for(cin>>n;n--;){cin>>word;T.in(word);}T.bulid();cin>>pas;bool sig=T.sea(pas);if(sig)printf("YES\n");else printf("NO\n");return 0; }

總結

以上是生活随笔為你收集整理的hiho一下 第四周 Hihocoder #1036 : Trie图的全部內容,希望文章能夠幫你解決所遇到的問題。

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