刷的一套字典树的题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=120748#overview
个人喜欢指针的字典树写法,但是大力喜欢数组的写法,反正是一个队的,互补一下反而更好- 。-本来前几题我的指针写法都是用new的,后来发现用new可能会超时,所以不如先静态的分配好足够的内存。因此,这样就需要和数组写法一样算节点数目了。引用一下铭神的原话:“节点数在最坏情况下可以认为是字符数量”,也就是说,节点大小应开单词数量乘以每个单词的长度上限。
A题,赤裸裸的字典树,直接给代码,但是不好的是,这题用的是new的方法创建新节点,要作模板的话不妨使用后面几题的代码。代码如下:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <set> 4 #include <math.h> 5 #include <vector> 6 #include <stack> 7 #include <map> 8 #include <string.h> 9 #define t_mid (l+r>>1) 10 #define ls (o<<1) 11 #define rs (o<<1 | 1) 12 #define lson ls,l,t_mid 13 #define rson rs,t_mid+1,r 14 using namespace std; 15 typedef long long ll; 16 17 struct node 18 { 19 int cnt; 20 node *child[26]; 21 node() 22 { 23 cnt=0; 24 for(int i=0;i<26;i++) child[i]=NULL; 25 } 26 }; 27 28 node *root = new node(); 29 30 void Insert(char *s) 31 { 32 node *p = root; 33 int len = strlen(s); 34 for(int i=0;i<len;i++) 35 { 36 int m = s[i] - 'a'; 37 if(p->child[m] != NULL) 38 { 39 p = p->child[m]; 40 p->cnt++; 41 } 42 else 43 { 44 node *newnode = new node(); 45 p->child[m] = newnode; 46 p = newnode; 47 p->cnt++; 48 } 49 } 50 } 51 52 int Search(char *s) 53 { 54 int len = strlen(s); 55 node *p = root; 56 for(int i=0;i<len;i++) 57 { 58 int m = s[i] - 'a'; 59 if(p->child[m] == NULL) 60 { 61 return 0; 62 } 63 else p = p->child[m]; 64 } 65 return p->cnt; 66 } 67 68 int main() 69 { 70 char s[15]; 71 while(gets(s) != NULL && strcmp(s,"")) 72 { 73 Insert(s); 74 } 75 while(gets(s) != NULL) 76 { 77 printf("%d ",Search(s)); 78 } 79 return 0; 80 }
B题,把数字化成二进制,然后从高位到低位进行储存,然后匹配的时候,因为是异或,所以用相反的数字匹配就行(1的话匹配字典树里面0的路径,反之亦然)。
这题的代码一开始不知道怎么下手,还是借鉴了别人的代码的= =。代码如下:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 using namespace std; 5 typedef long long ll; 6 7 const int N = 34; 8 const int M = N * 1e5; 9 int n,m,p,root; 10 ll a[N]; 11 12 struct Node 13 { 14 int l,r; 15 int val; 16 void clear() 17 { 18 l=r=-1; 19 } 20 }node[M]; 21 22 void Insert(int &root,int deep,ll x) 23 { 24 if(root == -1) 25 { 26 root = p++; 27 node[root].clear(); 28 } 29 30 if(deep == -1) {node[root].val = x;return;} 31 32 if(x & a[deep]) Insert(node[root].r,deep-1,x); //如果这个数的当前位是1,则向右边走 33 else Insert(node[root].l,deep-1,x); 34 } 35 36 void Query(int root,int deep,ll x) 37 { 38 if(deep == -1) 39 { 40 printf("%d ",node[root].val); 41 return; 42 } 43 44 if((x & a[deep]) && node[root].l != -1 || node[root].r == -1) 45 { 46 Query(node[root].l,deep-1,x); 47 } 48 else 49 { 50 Query(node[root].r,deep-1,x); 51 } 52 } 53 54 int main() 55 { 56 int T,cnt=1; 57 scanf("%d",&T); 58 a[0]=1; 59 for(int i=1;i<N;i++) a[i]=a[i-1]<<1; 60 61 while(T--) 62 { 63 printf("Case #%d: ",cnt++); 64 root = -1,p=0; 65 scanf("%d%d",&n,&m); 66 for(int i=1;i<=n;i++) 67 { 68 int t; 69 scanf("%d",&t); 70 Insert(root,N,(ll)t); 71 } 72 73 while(m--) 74 { 75 int t; 76 scanf("%d",&t); 77 Query(0,N,(ll)t); 78 } 79 } 80 }
C题,和B题很像,题意是任意从字典树中取出两个元素,求它们的和和剩下的元素中任意一个进行异或,得到的最大的答案是多少。一开始看了一个人的博客,那个人写的很烦,以为是难题,但是后来参考了另外一个人的代码,发现就是B题。思路很简单,枚举取出的元素,然后将这两个元素从字典树中删除!然后进行匹配,匹配完以后加回去即可。这样,删除操作其实就是Insert操作中,把cnt+1改成-1即可,所以可以把Insert改成update就可以实现两个功能了,具体见代码:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 using namespace std; 5 typedef long long ll; 6 7 const int N = 34; 8 int num[1000+5]; 9 10 struct node 11 { 12 int val; 13 int cnt; 14 node *child[2]; 15 node() 16 { 17 cnt=0; 18 val = -1; 19 for(int i = 0 ; i < 2 ; i ++) 20 { 21 child[i] = NULL; 22 } 23 } 24 }; 25 26 node *root; 27 28 void Freedom(node *p) 29 { 30 for(int i=0 ; i < 2 ; i ++) 31 { 32 if(p->child[i] != NULL) Freedom(p->child[i]); 33 } 34 35 delete p; 36 } 37 38 void update(node *p,int deep,ll x,int op) 39 { 40 if(deep == -1) {p->val = x;return;} 41 42 int m = ( (x>>deep) & 1 ); 43 if(p->child[m] == NULL) 44 { 45 node *newnode = new node(); 46 p->child[m] = newnode; 47 } 48 49 p->child[m]->cnt += op; 50 update(p->child[m],deep-1,x,op); 51 } 52 53 int Query(node *p,int deep,ll x) 54 { 55 if(deep == -1) return (int)p->val; 56 57 int m = 1 - ( (x>>deep) & 1 ) ; 58 if(p->child[m] != NULL && p->child[m]->cnt != 0) 59 { 60 return Query(p->child[m],deep-1,x); 61 } 62 else 63 { 64 return Query(p->child[1-m],deep-1,x); 65 } 66 } 67 68 int main() 69 { 70 int T,cnt=1; 71 scanf("%d",&T); 72 73 while(T--) 74 { 75 root = new node(); 76 77 int n; 78 scanf("%d",&n); 79 for(int i=1;i<=n;i++) 80 { 81 scanf("%d",num+i); 82 update(root,N,(ll)num[i],1); 83 } 84 85 86 int ans=0; 87 for(int i=1;i<=n;i++) 88 { 89 for(int j=i+1;j<=n;j++) 90 { 91 int t1 = num[i] + num[j]; 92 93 update(root,N,num[i],-1); //从字典树里删除这两个数 94 update(root,N,num[j],-1); 95 96 int t2 = Query(root,N,(ll)t1); //查找最大的值 97 98 ans = max(ans,t1^t2); 99 100 update(root,N,num[i],1); //将这两个数再次放回字典树里面 101 update(root,N,num[j],1); 102 103 } 104 } 105 106 printf("%d ",ans); 107 108 Freedom(root); //释放内存 109 } 110 }
同时,从这题中,还有一个对new出来的节点进行delete的操作,要释放内存。所以以后干脆就不要写new的字典树写法好了。。
D题,给出一堆的单词,找出每个单词的最短缩写,要求是每个缩写必须能认出这个单词,打个比方,app和apple,那么app作为app的缩写以后,apple的缩写就必须为appl。那么思路就很简单了:对一个单词,不断地匹配,直到一个节点,这个节点的cnt值为1或者这个点已经是这个单词的最后一个字母了,那么到此为止了。可以从上面这两个单词中很好的理解。虽然这题是我独立做出来的,但是查询操作还是写麻烦了,其实不需要用队列来装,只要该节点的cnt值大于1,就输出这个字母即可,直到为1或者单词结束为止。具体见代码:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <queue> 5 using namespace std; 6 typedef long long ll; 7 8 int num[1000+5]; 9 char s[1000+5][25]; 10 11 struct node 12 { 13 int cnt; 14 node *child[26]; 15 node() 16 { 17 cnt=0; 18 for(int i = 0 ; i < 26 ; i ++) 19 { 20 child[i] = NULL; 21 } 22 } 23 }; 24 25 node *root; 26 27 void Freedom(node *p) 28 { 29 for(int i=0 ; i < 26 ; i ++) 30 { 31 if(p->child[i] != NULL) Freedom(p->child[i]); 32 } 33 34 delete p; 35 } 36 37 void Insert(char *s) 38 { 39 node *p = root; 40 for(int i=0;s[i];i++) 41 { 42 int m = s[i] - 'a'; 43 if(p->child[m] == NULL) 44 { 45 p->child[m] = new node(); 46 } 47 p = p->child[m]; 48 p->cnt++; 49 } 50 } 51 52 void Query(char *s) 53 { 54 node *p = root; 55 queue<char> Q; 56 for(int i=0;s[i];i++) 57 { 58 int m = s[i] - 'a'; 59 if(p->child[m]->cnt == 1) 60 { 61 while(!Q.empty()) 62 { 63 char c = Q.front();Q.pop(); 64 putchar(c); 65 } 66 printf("%c ",s[i]); 67 return; 68 } 69 else 70 { 71 Q.push(s[i]); 72 } 73 p = p->child[m]; 74 } 75 76 while(!Q.empty()) 77 { 78 char c = Q.front();Q.pop(); 79 putchar(c); 80 } 81 puts(""); 82 } 83 84 int main() 85 { 86 int cnt = 0; 87 root = new node(); 88 while(gets(s[cnt++]) != NULL) 89 { 90 Insert(s[cnt-1]); 91 //if(cnt==12) break; 92 } 93 94 for(int i=0;i<cnt;i++) 95 { 96 printf("%s ",s[i]); 97 Query(s[i]); 98 } 99 }
E题的意思,给出一堆的字符串,如果有一个字符串是另外一个的前缀,那么输出NO,否则输出YES。思路和上题类似,枚举所有字符串,一直到结尾为止,如果结尾那个字母出现的次数大于1,那么这个字符串肯定是另外一个字符串的前缀了。具体见代码:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <queue> 5 using namespace std; 6 typedef long long ll; 7 8 int n,tot=0; 9 char s[10000+5][15]; 10 11 struct node 12 { 13 int cnt; 14 node *child[10]; 15 void clear() 16 { 17 cnt=0; 18 for(int i = 0 ; i < 10 ; i ++) 19 { 20 child[i] = NULL; 21 } 22 } 23 }N[100000+5]; 24 25 node *root; 26 27 node *newnode() 28 { 29 node *p = &N[tot++]; 30 p->clear(); 31 return p; 32 } 33 34 void Freedom(node *p) 35 { 36 for(int i=0 ; i < 10 ; i ++) 37 { 38 if(p->child[i] != NULL) Freedom(p->child[i]); 39 } 40 41 delete p; 42 } 43 44 void Insert(char *s) 45 { 46 node *p = root; 47 for(int i=0;s[i];i++) 48 { 49 int m = s[i] - '0'; 50 if(p->child[m] == NULL) 51 { 52 p->child[m] = newnode(); 53 } 54 p = p->child[m]; 55 p->cnt++; 56 } 57 } 58 59 bool Isok(char *s) 60 { 61 node *p = root; 62 for(int i=0;s[i];i++) 63 { 64 int m = s[i] - '0'; 65 p = p->child[m]; 66 } 67 if(p->cnt == 1) return true; 68 else return false; 69 } 70 71 int main() 72 { 73 int T; 74 scanf("%d",&T); 75 while(T--) 76 { 77 tot=0; 78 root = newnode(); 79 scanf("%d",&n); 80 for(int i=1;i<=n;i++) 81 { 82 scanf("%s",s[i]); 83 Insert(s[i]); 84 } 85 86 int flag=1; 87 for(int i=1;i<=n;i++) 88 { 89 if(!Isok(s[i])) {flag=0;break;} 90 } 91 if(!flag) puts("NO"); 92 else puts("YES"); 93 } 94 }
F题,意思是找出字典中的所有串,这个串是由字典中的另外两个串拼成的。第一次做时,n^2拼接然后查找,结果当然是TLE,GG。。然后用了大力的方法:比方说三个单词,abc,qwe,abcqwe。查找abcqwe的时候,到c时,c是某个单词(abc)的结尾并且c的下一个字母并不是abcqwe的结尾,让下一个字母(q)入栈,到时候取出栈内所有元素,然后对每个元素进行继续匹配,如果到最后一个字母时这个字母是另外一个单词的结尾,那么这个单词(abcqwe)就是满足题意的。。这题还是有一点技巧性的,比方说栈内存的是那个字母的位置而不是char。关于判断是不是另外单词的结尾,只要在节点处在设置一个布尔类型的变量ised即可。具体见代码吧:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <queue> 5 #include <stack> 6 using namespace std; 7 typedef long long ll; 8 9 int n,tot=0; 10 char s[50000+5][15]; 11 12 struct node 13 { 14 int cnt; 15 bool ised; 16 node *child[26]; 17 void clear() 18 { 19 cnt = 0; 20 ised = false; 21 for(int i = 0 ; i < 26 ; i ++) 22 { 23 child[i] = NULL; 24 } 25 } 26 }N[20*(50000+5)]; 27 28 node *root; 29 30 node *newnode() 31 { 32 node *p = &N[tot++]; 33 p->clear(); 34 return p; 35 } 36 37 void Freedom(node *p) 38 { 39 for(int i=0 ; i < 26 ; i ++) 40 { 41 if(p->child[i] != NULL) Freedom(p->child[i]); 42 } 43 44 delete p; 45 } 46 47 void Insert(char *s) 48 { 49 node *p = root; 50 for(int i=0;s[i];i++) 51 { 52 int m = s[i] - 'a'; 53 if(p->child[m] == NULL) 54 { 55 p->child[m] = newnode(); 56 } 57 p = p->child[m]; 58 p->cnt++; 59 } 60 p->ised = true; 61 } 62 63 bool Query(char *s) 64 { 65 stack<int> S; 66 node *p = root; 67 for(int i=0;s[i];i++) 68 { 69 int m = s[i] - 'a'; 70 p = p->child[m]; 71 if(p->ised == true && s[i+1]) S.push(i+1); 72 } 73 74 while(!S.empty()) 75 { 76 p = root; 77 int x = S.top();S.pop(); 78 for(int i=x;s[i];i++) 79 { 80 int m = s[i] - 'a'; 81 if(p->child[m] == NULL) break; 82 else p = p->child[m]; 83 if(!s[i+1] && p->ised == true) return true; 84 } 85 } 86 return false; 87 } 88 89 int main() 90 { 91 int cnt = 0; 92 tot = 0; 93 root = newnode(); 94 while(gets(s[cnt++]) != NULL) 95 { 96 Insert(s[cnt-1]); 97 //if(cnt == 6) break; 98 } 99 100 for(int i=0;i<cnt;i++) 101 { 102 if(Query(s[i])) puts(s[i]); 103 } 104 }
最后一题也是很吊的一题。题意是模仿手机上的九格输入法,输入了一系列的单词。然后给你一些数字,表示现在的输入,问每次输入一个数字,按照频率,最可能是在输入什么内容。。具体的题意还是看原题吧。然后思路就是同样的匹配,把每一个数字当做一个状态,对这个状态进行若干个方向的选择,选择频率最大的内容即可。具体见代码吧:

1 #include <stdio.h> 2 #include <algorithm> 3 #include <string.h> 4 #include <queue> 5 #include <stack> 6 #include <string> 7 #include<iostream> 8 using namespace std; 9 typedef long long ll; 10 11 int n,tot=0,fre; 12 string ans; 13 char str[100+5]; 14 int cnt[10] = {0,0,3,3,3,3,3,4,3,4}; 15 char ch[10][5] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; 16 17 struct node 18 { 19 int cnt; 20 node *child[26]; 21 void clear() 22 { 23 cnt = 0; 24 for(int i = 0 ; i < 26 ; i ++) 25 { 26 child[i] = NULL; 27 } 28 } 29 }N[100000+5]; 30 31 node *root; 32 33 node *newnode() 34 { 35 node *p = &N[tot++]; 36 p->clear(); 37 return p; 38 } 39 40 void Freedom(node *p) 41 { 42 for(int i=0 ; i < 26 ; i ++) 43 { 44 if(p->child[i] != NULL) Freedom(p->child[i]); 45 } 46 47 delete p; 48 } 49 50 void Insert(char *s,int t) 51 { 52 node *p = root; 53 for(int i=0;s[i];i++) 54 { 55 int m = s[i] - 'a'; 56 if(p->child[m] == NULL) 57 { 58 p->child[m] = newnode(); 59 } 60 p = p->child[m]; 61 p->cnt += t; 62 } 63 } 64 65 void Query(int st,int ed,node *p,string s) 66 { 67 if(st == ed) 68 { 69 if(p->cnt > fre) 70 { 71 fre = p->cnt; 72 ans = s; 73 } 74 } 75 76 int num = str[st] - '0'; 77 for(int i=0;i<cnt[num];i++) 78 { 79 //对这个键的各个方向进行试探性的选择 80 int m = ch[num][i] - 'a'; 81 if(p->child[m] != NULL) Query(st+1,ed,p->child[m],s+ch[num][i]); 82 } 83 } 84 85 int main() 86 { 87 int T; 88 scanf("%d",&T); 89 for(int kase=1;kase<=T;kase++) 90 { 91 tot = 0; 92 root = newnode(); 93 printf("Scenario #%d: ",kase); 94 int n; 95 scanf("%d",&n); 96 for(int i=1;i<=n;i++) 97 { 98 char s[100+5]; 99 int t; 100 scanf("%s%d",s,&t); 101 Insert(s,t); 102 } 103 104 scanf("%d",&n); 105 while(n--) 106 { 107 scanf("%s",str); 108 int len = strlen(str); 109 for(int i=1;i<len;i++) 110 { 111 fre = 0; 112 Query(0,i,root,""); 113 if(fre) cout << ans << endl; 114 else puts("MANUALLY"); 115 } 116 puts(""); 117 } 118 puts(""); 119 } 120 }
那么,字典树这个专题大概是完了,依稀记得上次百度之星有一道是字典树的没做,lyf给了一个很好的板子,下次有空了再补上。= =!