1. 程式人生 > >hiho兄弟的字典樹之爭(hiho1014)

hiho兄弟的字典樹之爭(hiho1014)

應該 http targe 說道 eight 最壞情況 傳說 信息 字母

小Hi和小Ho是一對好朋友,出生在信息化社會的他們對編程產生了莫大的興趣,他們約定好互相幫助,在編程的學習道路上一同前進。

這一天,他們遇到了一本詞典,於是小Hi就向小Ho提出了那個經典的問題:“小Ho,你能不能對於每一個我給出的字符串,都在這個詞典裏面找到以這個字符串開頭的所有單詞呢?

身經百戰的小Ho答道:“怎麽會不能呢!你每給我一個字符串,我就依次遍歷詞典裏的所有單詞,檢查你給我的字符串是不是這個單詞的前綴不就是了?

小Hi笑道:“你啊,還是太年輕了!~假設這本詞典裏有10萬個單詞,我詢問你一萬次,你得要算到哪年哪月去?”

小Ho低頭算了一算,看著那一堆堆的0,頓時感覺自己這輩子都要花在上面了...

小Hi看著小Ho的囧樣,也是繼續笑道:“讓我來提高一下你的知識水平吧~你知道樹這樣一種數據結構麽?”

小Ho想了想,說道:“知道~它是一種基礎的數據結構,就像這裏說的一樣!”

小Hi滿意的點了點頭,說道:“那你知道我怎麽樣用一棵樹來表示整個詞典麽?”

小Ho搖搖頭表示自己不清楚。

提示一:Trie樹的建立:

小Hi於是在紙上畫了一會,遞給小Ho,道:“你看這棵樹和這個詞典有什麽關系?”

技術分享

小Ho盯著手裏的紙想了一會道:“我知道了!對於從樹的根節點走到每一個黑色節點所經過的路徑,如果將路徑上的字母都連起來的話,就都對應著詞典中的一個單詞呢!

小Hi說道:“那你知道如何根據一個詞典構建這樣一棵樹麽?”

“不造!”

“想你也不知道,我來告訴你吧~”小Hi擺出一副老師的樣子,說道:“你先這麽想,如果我已經有了這樣的一個詞典和對應的一棵樹,我要添加一個新的單詞apart,我應該怎麽做?”

“讓我想想……”小Ho又開始苦思冥想:“首先我要先看看已經能走到哪一步了對吧?比如我從1號節點走"a"這一條邊就可以走到2號節點,然後從2號節點走"p"這一條邊可以走到3號節點,然後……就沒路可走了!這時候我就需要添加一條從3號節點出發且標記為"p"的邊才可以接著往下走……最後就是這樣了!然後我把最後到達的這個結點標記為黑色就可以了。”

技術分享

小Hi說道:“真聰明~那你不妨再算算如果是一個有10W個單詞的詞典,每個單詞的長度不超過10的話,這棵樹會有多大?”

小Ho於是掏出筆來,一邊畫一遍念叨:“假設我已經將前三個單詞構成了這樣一棵樹,那麽我要添加一個新的單詞的時候,最壞情況是這個單詞和之前的三個單詞都沒有公共前綴,那麽這個新的單詞的長度如果是5的話,我就至少要添加5個結點到樹中才能夠繼續表示這個詞典!”

“而如果每次都是最壞情況的話,這棵樹最多也就100W個結點這麽大!更何況最壞情況是不可能次次都發生的!畢竟字母表也才26個字母呢!”小Ho繼續說道。

“嗯~這樣我們是不是就可以用(單詞個數*單詞長度)個結點來表示一個詞典了呢?小Hi問道。

“是的呢!”小Ho道:“但是這樣一棵樹又有什麽用呢?”

“可別小看了它,它就是傳說中的Trie樹哦~至於他有什麽用,一會你就知道了!”小Hi笑嘻嘻的回答道。

提示二:如何使用Trie樹:

小Hi在樹上用綠色標出了一個節點,遞給小Ho。

技術分享

“這個結點……是從根節點先走"a"然後走"p"到達的結點呢!哦~~我知道了,以這個結點為根的子樹裏所有標記結點都是以"ap"為前綴的單詞呢!而且所有以"ap"為前綴的單詞都在以這個節點為根的子樹裏~”小Ho驚喜道。

“是的呢~那你對怎麽解決我的問題有想法了麽?”小Hi追問道。

“唔...那就是每次拿到你的字符串之後,我在樹上找到其對應的那個結點,然後統計這個節點中有多少個標記節點?”小Ho不是很確定的答道:“但是這樣...似乎在最壞情況,也就是你每次給個字符串都很短的時候,我還是要掃描這棵樹的很大一部分呢?也就是說雖然平均時間復雜度降低了,但是最壞情況時間復雜度還是很高的樣子!”

小Hi笑嘻嘻道:”沒想到你自己看出來了呢~我還以為又要教訓你了!~那你有什麽好的解決方法麽?”

“沒呢!小Hi你就別賣關子了,趕緊告訴我吧!”被折磨的夠嗆的小Ho開始求饒。

“好吧!就幫你這一回~”

提示三:在建立Trie樹時同時進行統計!

“小Ho你有沒有想過這樣一個問題?不妨稱以T為根的子樹中標記節點的個數為L[T],既然我要統計某個L[T1],,而這個結點是不確定的,我有沒有辦法一次性把所有結點的L[T]求出來呢?”小Hi整理了下思緒,問道。

“似乎是有的,老師以前說過,遞歸什麽的。。”小Ho答道。

“遞歸太復雜了!我們可以之後再說,你這麽想,在你構建Trie樹的時候,當你經過一個結點的時候,說明了什麽?”小Hi撇了撇頭,繼續問道。

“我想想,經過一個結點……標記結點……說明了以這個結點為根的子樹中將要多出來一個標記結點?”

“沒錯!那你有沒有什麽辦法來記錄這個改變呢?”

“我想想,我在最開始置所有L[T]=0,然後每次添加一個新的單詞的時候,都將它經過的所有結點的L[T]全部+1,這樣我構建完這棵Trie樹的時候,我也就能夠同時統計到所有L[T]了,對麽?”小Ho開心道。

Input

輸入的第一行為一個正整數n,表示詞典的大小,其後n行,每一行一個單詞(不保證是英文單詞,也有可能是火星文單詞哦),單詞由不超過10個的小寫英文字母組成,可能存在相同的單詞,此時應將其視作不同的單詞。接下來的一行為一個正整數m,表示小Hi詢問的次數,其後m行,每一行一個字符串,該字符串由不超過10個的小寫英文字母組成,表示小Hi的一個詢問。

在20%的數據中n, m<=10,詞典的字母表大小<=2.

在60%的數據中n, m<=1000,詞典的字母表大小<=5.

在100%的數據中n, m<=100000,詞典的字母表大小<=26.

本題按通過的數據量排名哦~

Output

對於小Hi的每一個詢問,輸出一個整數Ans,表示詞典中以小Hi給出的字符串為前綴的單詞的個數。

Sample Input

5
babaab
babbbaaaa
abba
aaaaabaa
babaababb
5
babb
baabaaa
bab
bb
bbabbaab

Sample Output

1
0
3
0
0
 1 #include<iostream>
 2 #include<string.h>
 3 #include<cstdio> //可改為#include<bits/stdc++.h>
 4 using namespace std;
 5 int wum[100000*26][26];
 6 int num[100000*26];
 7 int cnt = 1;
 8 void insert(char *str)
 9 {
10     int root=0;
11     int x;
12     for(int i=0;str[i];i++)
13     {
14         x=str[i]-a;
15         if(!wum[root][x])
16             wum[root][x]=cnt++;
17         num[root]++;
18         root=wum[root][x];
19     }
20     num[root]++;
21 }
22 int search(char *str)
23 {
24     int root=0;
25     int x;
26     for(int i=0;str[i];i++)
27     {
28         x=str[i]-a;
29         if(wum[root][x])
30             root=wum[root][x];
31         else
32             return 0;
33     }
34     return num[root];
35 }
36 int main()
37 {
38     int n,m;
39     char str[20];
40     scanf("%d",&n);
41     for(int i=1;i<=n;i++)
42     {
43         scanf("%s",str);
44         insert(str);
45     }
46     scanf("%d",&m);
47     for(int j=1;j<=m;j++)
48     {
49         scanf("%s",str);
50         printf("%d\n",search(str));
51     }
52     return 0;
53 }

hiho兄弟的字典樹之爭(hiho1014)