HDU - 1251 - 统计难题
题目:
统计难题
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131070/65535 K (Java/Others)
Total Submission(s): 14095 Accepted Submission(s): 6070
Problem Description
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).
Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.
注意:本题只有一组测试数据,处理到文件结束.
注意:本题只有一组测试数据,处理到文件结束.
Output
对于每个提问,给出以该字符串为前缀的单词的数量.
Sample Input
banana
band
bee
absolute
acm
ba
b
band
abc
Sample Output
2
3
1
0
题意不解释,这题RE+MLE+WA一共16次= =,主要是对Trie不太熟悉。首先我用的是静态链表,RE的主要原因是数组成二维了,这一点一开始没有考虑到,是因为没有仔细把大白书上面的模板看完,以为用来标记的数组应该也是二维的,这样才合逻辑,虽然小数据的话没有出问题,但是数据大到一定情况的话就会RE,这是就会认为是不是数组开得不够大,于是继续加大,接着就是MLE了= =。
用来标记的附加数组这里只需要使用一维的就可以,为什么?因为这里字典树的本体用的是一个二维数组,其中一维是用来表示某个前缀的下一个字母有可能是哪些(这里这种解释似乎有点不是很准确,建议去看一下Trie树的构成),另一维是用来表示可以最多可以保存多少个字母的长度(这种说法也好像不是很准确)。附加数组的规模需要和字典树的另一维一样大,这样才可以起作用。其实就是把静态链表的结构看成指针链表(就是把原本整齐的内存结构看成游离的内存块,这样说似乎更加生动,容易理解= =) 那么附加数组就像是紧贴Trie数的一条链,它的作用就是标明某个位置就是一个字符串的结束,就像字符串末尾的'\0'一样。
至于上面说的规模需要开多大,我觉得这里需要根据问题来决定,如果他说有10000个字符串,每个字符串最长就有10的长度,每个位置有10种字符出现的可能的话,规模大概就需要10000*10*10以上了,这里我暂时还不是很确定,有可能可以小一点。
代码:
1 #include <stdio.h> 2 #include <string.h> 3 #define MAX 500000 4 using namespace std; 5 6 int s[MAX][26],count[MAX],tot; 7 8 void Init() 9 { 10 memset(s[0],0,sizeof(s[0])); 11 memset(count,0,sizeof(count)); 12 tot=1; 13 } 14 15 void Insert(char *c) 16 { 17 int i,j,u,n; 18 n=strlen(c); 19 j=0; 20 for(i=0;i<n;i++) 21 { 22 u=c[i]-'a'; 23 if(!s[j][u]) 24 { 25 memset(s[tot],0,sizeof(s[tot])); 26 count[tot]=0; 27 s[j][u]=tot++; 28 } 29 j=s[j][u]; 30 count[j]++; 31 } 32 } 33 34 35 int Query(char *c) 36 { 37 int i,j,u,n; 38 n=strlen(c); 39 j=0; 40 if(n>20) return 0; 41 for(i=0;i<n;i++) 42 { 43 u=c[i]-'a'; 44 if(!s[j][u]) return 0; 45 if(i+1==n) break; 46 j=s[j][u]; 47 } 48 return count[s[j][u]]; 49 } 50 51 int main() 52 { 53 int ans; 54 char c[100]; 55 Init(); 56 while(gets(c),c[0]!='\0') 57 { 58 Insert(c); 59 } 60 while(gets(c)) 61 { 62 ans=Query(c); 63 printf("%d\n",ans); 64 } 65 return 0; 66 }