Trie 字典树
1、UVa 1401 Remember the Word
题意:给出n个字符串集合,问其有多少种组合方式形成目标字符串。
思路:对n个字符串集合建立Trie树,保存每个结点的字符串的顺序编号。然后对这棵树查找目标字符串每一个后缀的前缀字符串,累加。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #include<vector> 6 using namespace std; 7 const int MOD = 20071027; 8 struct Trie 9 { 10 int word[4001 * 100][26]; 11 int ex[4001 * 100]; 12 int size; 13 14 void clear() 15 { 16 memset(word[0], 0, sizeof(word[0])); 17 size = 1; 18 ex[0] = 0; 19 } 20 21 void insert(char *s, int v) 22 { 23 int Now = 0, curchr; 24 for (int i = 0; s[i] != '\0'; i++) 25 { 26 int c = s[i] - 'a'; 27 if (!word[Now][c]) 28 { 29 memset(word[size], 0, sizeof(word[size])); 30 ex[size] = 0; 31 word[Now][c] = size++; 32 } 33 Now = word[Now][c]; 34 } 35 ex[Now] = v; 36 } 37 38 int search(char *s, int len, vector<int>& ans) 39 { 40 int u = 0, c; 41 for (int i = 0; s[i] != '\0'&&i<len; i++) 42 { 43 c = s[i] - 'a'; 44 if (!word[u][c])return 0; 45 u = word[u][c]; 46 if (ex[u]) 47 ans.push_back(ex[u]); 48 } 49 } 50 } tree; 51 52 char s[300001]; 53 char tmp[101]; 54 int dp[300001]; 55 int n; 56 int ll[4001];//记录每个字符串的长度 57 int main() 58 { 59 int Case = 1; 60 while (~scanf("%s",s)) 61 { 62 scanf("%d", &n); 63 tree.clear(); 64 memset(dp, 0, sizeof(dp)); 65 int len = strlen(s); 66 67 for (int i = 0; i<n; i++) 68 { 69 scanf("%s", tmp); 70 ll[i + 1] = strlen(tmp); 71 tree.insert(tmp, i + 1); 72 } 73 dp[len] = 1; 74 for (int i = len - 1; i >= 0; i--) 75 {//找下标为i~len-1的子字符串的前缀字符串 76 vector<int>ans; 77 tree.search(s + i, len - i, ans); 78 for (int j = 0; j<ans.size(); j++) 79 dp[i] = (dp[i] + dp[i + ll[ans[j]]]) % MOD; 80 } 81 printf("Case %d: %d\n", Case++, dp[0]); 82 } 83 return 0; 84 }
2、UVA 11732 strcmp() Anyone?
题意:给n个长度不超过1000的字符串,两两字符串比较,某一位相同比较2次,不相同比较1次,问需要比较多少次。
思路:左儿子右兄弟法表示Trie树。向右找相同字符,向左添加新字符,同一兄弟层表示在同一位的字符。
参考:http://www.cnblogs.com/sineatos/p/3888815.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <cstring> 3 #define MAXN 4001010 4 #define ll long long 5 using namespace std; 6 //左儿子右兄弟表示字典树 7 int head[MAXN];// head[i]为第i个结点的左儿子编号 8 int next[MAXN];// next[i]为第i个结点的右兄弟编号 9 int tot[MAXN];// tot[i]为第i个结点为根的子树包含的叶结点(即以包含其的前缀的字符串个数)总数 10 int ed[MAXN];// ed[i]为第i个结点结束的字符串的个数 11 char ch[MAXN];// ch[i]为第i个结点上的字符 12 int sz; 13 ll sum; 14 void init() 15 {// 初始时只有一个根结点 16 sz = 1; head[0] = next[0] = tot[0] = 0; sum = 0; 17 } 18 19 void insert(char *s) 20 { 21 int u, v, n = strlen(s); 22 u = 0; 23 for (int i = 0; i<n; i++) 24 { 25 bool f = 0; 26 for (v = head[u]; v != 0; v = next[v]) 27 {//找字符s[i] 28 if (ch[v] == s[i]) 29 { 30 f = 1; break; 31 } 32 } 33 if (!f) 34 {//如果没找到,新建结点 35 v = sz++; 36 tot[v] = 0; 37 ed[v] = 0; 38 ch[v] = s[i]; 39 next[v] = head[u];//插入首部 40 head[u] = v; 41 head[v] = 0; 42 } 43 u = v; 44 sum += tot[v] * 2; 45 tot[v]++; 46 } 47 sum += ed[u]; 48 ed[u]++; 49 } 50 int main() 51 { 52 int n; 53 char s[1002]; 54 int Case = 1; 55 while(~scanf("%d", &n)&&n) 56 { 57 init(); 58 for (int i = 0; i<n; i++) 59 { 60 scanf("%s", s); 61 insert(s); 62 } 63 printf("Case %d: %lld\n", Case++, sum + n*(n - 1) / 2); 64 } 65 return 0; 66 }
3、uva 11488 Hyper Prefix Sets
题意:求n个给出的01串的最长公共前缀的长度*n的值。
思路:字典树,每次插入一个新的01串,累计每个结点的前缀的长度。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 6 const int maxn = 10000010; 7 const int Char_size = 2; 8 struct Trie 9 { 10 int word[maxn][Char_size];//结点 11 int val[maxn];//附加信息 12 int size;//总结点数 13 int ans; 14 15 void Init() 16 { 17 memset(word[0], 0, sizeof(word[0])); 18 size = 1; 19 val[0] = 0; 20 ans = 0; 21 } 22 int getID(char c) 23 { 24 return c - '0'; 25 } 26 void Insert(char *s) 27 { 28 int now = 0, curchr; 29 for (int i = 0; s[i] != '\0'; i++) 30 { 31 int pos =getID(s[i]); 32 if (!word[now][pos]) 33 { 34 memset(word[size], 0, sizeof(word[size])); 35 val[size] = 0; 36 word[now][pos] = size++; 37 } 38 now = word[now][pos]; 39 val[now] += i + 1;//累加到该结点的前缀的长度 40 ans = max(ans, val[now]); 41 } 42 } 43 }tree; 44 45 char str[maxn]; 46 int main() 47 { 48 int t; 49 scanf("%d", &t); 50 while (t--) 51 { 52 tree.Init(); 53 int n; 54 scanf("%d", &n); 55 while (n--) 56 { 57 scanf("%s", str); 58 tree.Insert(str); 59 } 60 printf("%d\n", tree.ans); 61 } 62 return 0; 63 }
4、uva 1519 Dictionary Size
题意:给出n个可能重复的单词,由这些单词组成新单词,新单词可以就是原来的单词,或者新单词的前一部分为给出单词的非空前缀,后一部分为给出单词的非空后缀,求所有不同的新单词有多少个。
思路:字典树,建立前缀和后缀字典树,ans=前缀树的新增节点数*后缀树的新增节点数-重复情况+单字符单词情况。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 6 const int maxn = 400100; 7 const int Char_size = 26; 8 struct Trie 9 { 10 int word[maxn][Char_size];//结点 11 int Count[Char_size]; 12 int size;//总结点数 13 14 void Init() 15 { 16 memset(word[0], 0, sizeof(word[0])); 17 memset(Count, 0, sizeof(Count)); 18 size = 1; 19 } 20 int getID(char c) 21 { 22 return c - 'a'; 23 } 24 void Insert(char *s) 25 { 26 int now = 0, curchr; 27 for (int i = 0; s[i] != '\0'; i++) 28 { 29 int pos = getID(s[i]); 30 if (!word[now][pos]) 31 { 32 memset(word[size], 0, sizeof(word[size])); 33 word[now][pos] = size++; 34 if (i > 0) Count[pos]++; 35 } 36 now = word[now][pos]; 37 } 38 } 39 }pretree,suftree; 40 41 char str[maxn]; 42 int vis[Char_size];//统计只有一个字符的字符串的个数 43 int main() 44 { 45 int n; 46 while (~scanf("%d", &n)) 47 { 48 pretree.Init(); 49 suftree.Init(); 50 memset(vis, 0, sizeof(vis)); 51 while (n--) 52 { 53 scanf("%s", str); 54 int len = strlen(str); 55 if (len==1) vis[str[0] - 'a']=1; 56 pretree.Insert(str); 57 reverse(str, str + len); 58 suftree.Insert(str); 59 } 60 long long ans = 1ll * (pretree.size - 1)*(suftree.size - 1); 61 //如果某个字符在pretree出现x次,在suftree出现y次,考虑该字符所组成的情况则有(1+x)*(1+y)种,然而其中只有1+x+y种是不重复的,需要去掉x*y种 62 for (int i = 0; i < Char_size; i++) ans -= 1ll * pretree.Count[i] * suftree.Count[i]; 63 for (int i = 0; i < Char_size; i++) ans += vis[i];//单字符字符串另外判断 64 printf("%lld\n", ans); 65 } 66 return 0; 67 }
5、uva 1385 Billing Tables
题意:给出旧电话表,每个记录表示电话前缀为该区间的收费方式,注意,当一个电话号码可以被多个区间包含,以排在第一个的记录为准,当然,有些收费方式invalid。现在你要新建电话表,去掉invalid的信息,同时每个记录表示电话号码的一个前缀的收费方式。要求以字典序输出同时保证条目最少(即如果存在70~79前缀,则可合并为7).
思路:因为记录以先出现为准,所以要倒序插入每个旧表的记录,同时标记哪些是invalid.最后从小到大(位数从小到大,每一位数字从小到大)DFS即可,一次统计(针对“即如果存在70~79前缀,则可合并为7”可以向上合并条目信息),一次输出。
下面两份代码主要是处理重复的代表收费方式的字符串的方法有别。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<map> 3 #include<string> 4 #include<cstring> 5 using namespace std; 6 map<string, int>bp; 7 const int maxn = 110; 8 char bplan[maxn][30]; 9 char ql[maxn][30]; 10 char qr[maxn][30]; 11 char tmp[30]; 12 int isvalid[maxn]; 13 14 const int maxw = 4010 * 10; 15 const int Char_size = 10; 16 struct Trie 17 { 18 int w[maxw][Char_size]; 19 int val[maxw]; 20 int size; 21 int ans; 22 23 void Init() 24 { 25 memset(w[0], 0, sizeof(w[0])); 26 val[0] = 0; 27 size = 1; 28 ans = 0; 29 } 30 int GetID(char c) 31 { 32 return c - '0'; 33 } 34 void Insert(char *sl, char *sr, int v) 35 { 36 int len = strlen(sl); 37 int nowl = 0, nowr = 0; 38 for (int i = 0; i < len; i++) 39 { 40 //对已有结点拓展其所有子节点,并向下更新节点信息 41 for (int j = 0; j < Char_size; j++) 42 { 43 if (!w[nowl][j]) 44 { 45 memset(w[size], 0, sizeof(w[size])); 46 val[size] = 0; 47 w[nowl][j] = size++; 48 } 49 if (val[nowl]) val[w[nowl][j]] = val[nowl]; 50 if (!w[nowr][j]) 51 { 52 memset(w[size], 0, sizeof(w[size])); 53 val[size] = 0; 54 w[nowr][j] = size++; 55 } 56 if (val[nowr]) val[w[nowr][j]] = val[nowr]; 57 } 58 59 val[nowl] = val[nowr] = 0;//已经向下后删除标记 60 61 //更新区域内的标记 62 if (nowl == nowr)//比如7600和7899,更新76~78(不包括76,78),边界将由下次更新 63 { 64 for (int j = GetID(sl[i]) + 1; j < GetID(sr[i]); j++) 65 { 66 val[w[nowl][j]] = v; 67 } 68 } 69 else//比如7600和7899,更新760~769(不包括760)和780~789(不包括789),无需更新77X。760和789边界将下次考虑 70 { 71 for (int j = GetID(sl[i]) + 1; j < Char_size; j++) val[w[nowl][j]] = v; 72 for (int j = 0; j < GetID(sr[i]); j++) val[w[nowr][j]] = v; 73 } 74 75 nowl = w[nowl][GetID(sl[i])];//最后结尾更新封闭区间最左侧和最右侧 76 nowr = w[nowr][GetID(sr[i])]; 77 } 78 val[nowl] = v; 79 val[nowr] = v; 80 } 81 82 void DFS(int now) 83 { 84 if (val[now]) 85 { 86 ans += isvalid[val[now]]; 87 return; 88 } 89 if (!w[now][0]) return; 90 91 bool flag = true; 92 for (int i = 0; i < Char_size; i++) 93 { 94 DFS(w[now][i]); 95 if (val[w[now][i]] != val[w[now][0]]) flag = false; 96 } 97 //合并 98 if (flag&&isvalid[val[w[now][0]]] && now != 0) 99 { 100 ans -= 9; 101 val[now] = val[w[now][0]]; 102 } 103 } 104 105 char pt[15]; 106 void Print(int now, int len) 107 { 108 if (val[now]) 109 { 110 if (isvalid[val[now]])printf("%s %s\n", pt,bplan[val[now]]); 111 return; 112 } 113 if (!w[now][0]) return; 114 for (int i = 0; i < 10; i++) 115 { 116 pt[len] = '0' + i; 117 pt[len + 1] = '\0'; 118 Print(w[now][i], len + 1); 119 } 120 } 121 }tree; 122 123 124 int main() 125 { 126 int Case = 1; 127 int n; 128 while (~scanf("%d", &n)) 129 { 130 if (Case > 1) printf("\n"); 131 tree.Init(); 132 bp.clear(); 133 memset(isvalid, 0, sizeof(isvalid)); 134 for (int i = 1; i <= n; i++) 135 { 136 scanf("%s", ql[i]); 137 scanf("%s", tmp);//'-' 138 scanf("%s", tmp); 139 int len1 = strlen(ql[i]); 140 int len2 = strlen(tmp); 141 memset(qr[i], 0, sizeof(qr[i]));//这句要有 142 strncpy(qr[i], ql[i], len1 - len2); 143 strcat(qr[i], tmp); 144 scanf("%s", bplan[i]); 145 string s = bplan[i]; 146 if (!bp.count(s)) 147 { 148 bp[s] = i; 149 } 150 if (strcmp(bplan[i], "invalid")) isvalid[i] = 1; 151 } 152 for (int i = n; i >= 1; i--) 153 { 154 string s = bplan[i]; 155 tree.Insert(ql[i], qr[i], bp[s]); 156 } 157 tree.DFS(0); 158 printf("%d\n", tree.ans); 159 tree.Print(0, 0); 160 Case++; 161 } 162 return 0; 163 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<map> 3 #include<string> 4 #include<cstring> 5 using namespace std; 6 const int maxn = 110; 7 char bplan[maxn][30]; 8 char ql[maxn][30]; 9 char qr[maxn][30]; 10 char tmp[30]; 11 int isvalid[maxn]; 12 int num[maxn]; 13 14 const int maxw = 4010*10; 15 const int Char_size = 10; 16 struct Trie 17 { 18 int w[maxw][Char_size]; 19 int val[maxw]; 20 int size; 21 int ans; 22 23 void Init() 24 { 25 memset(w[0], 0, sizeof(w[0])); 26 val[0] = 0; 27 size = 1; 28 ans = 0; 29 } 30 int GetID(char c) 31 { 32 return c - '0'; 33 } 34 void Insert(char *sl,char *sr, int v) 35 { 36 int len = strlen(sl); 37 int nowl =0, nowr = 0; 38 for (int i = 0; i < len; i++) 39 { 40 //对已有结点拓展其所有子节点,并向下更新节点信息 41 for (int j = 0; j < Char_size; j++) 42 { 43 if (!w[nowl][j]) 44 { 45 memset(w[size], 0, sizeof(w[size])); 46 val[size] = 0; 47 w[nowl][j] = size++; 48 } 49 if (val[nowl]) val[w[nowl][j]] = val[nowl]; 50 if (!w[nowr][j]) 51 { 52 memset(w[size], 0, sizeof(w[size])); 53 val[size] = 0; 54 w[nowr][j] = size++; 55 } 56 if (val[nowr]) val[w[nowr][j]] = val[nowr]; 57 } 58 59 val[nowl] = val[nowr] = 0;//已经向下后删除标记 60 61 //更新区域内的标记 62 if (nowl == nowr)//比如7600和7899,更新76~78(不包括76,78),边界将由下次更新 63 { 64 for (int j = GetID(sl[i]) + 1; j < GetID(sr[i]); j++) 65 { 66 val[w[nowl][j]] = v; 67 } 68 } 69 else//比如7600和7899,更新760~769(不包括760)和780~789(不包括789),无需更新77X。760和789边界将下次考虑 70 { 71 for (int j = GetID(sl[i]) + 1; j < Char_size; j++) val[w[nowl][j]] = v; 72 for (int j = 0; j < GetID(sr[i]); j++) val[w[nowr][j]] = v; 73 } 74 75 nowl = w[nowl][GetID(sl[i])]; 76 nowr = w[nowr][GetID(sr[i])]; 77 } 78 val[nowl] = v;//最后结尾更新封闭区间最左侧和最右侧 79 val[nowr] = v; 80 } 81 82 void DFS(int now) 83 { 84 if (val[now]) 85 { 86 ans += isvalid[val[now]]; 87 return; 88 } 89 if (!w[now][0]) return; 90 91 bool flag = true; 92 for (int i = 0; i < Char_size; i++) 93 { 94 DFS(w[now][i]); 95 if (val[w[now][i]] != val[w[now][0]]) flag = false; 96 } 97 //合并 98 if (flag&&isvalid[val[w[now][0]]] && now != 0) 99 { 100 ans -= 9; 101 val[now] = val[w[now][0]]; 102 } 103 } 104 105 char pt[maxn]; 106 void Print(int now, int len) 107 { 108 if (val[now]) 109 { 110 if(isvalid[val[now]])printf("%s %s\n", pt,bplan[val[now]]); 111 return; 112 } 113 if (!w[now][0]) return; 114 for (int i = 0; i < 10; i++) 115 { 116 pt[len] = '0' + i; 117 pt[len + 1] = '\0'; 118 Print(w[now][i], len + 1); 119 } 120 } 121 }tree; 122 123 124 int main() 125 { 126 int Case = 1; 127 int n; 128 while (~scanf("%d", &n)) 129 { 130 if (Case > 1) printf("\n"); 131 tree.Init(); 132 memset(isvalid, 0, sizeof(isvalid)); 133 for (int i = 1; i <= n; i++) 134 { 135 scanf("%s", ql[i]); 136 scanf("%s", tmp);//'-' 137 scanf("%s", tmp); 138 int len1 = strlen(ql[i]); 139 int len2 = strlen(tmp); 140 memset(qr[i], 0, sizeof(qr[i]));//这句要有 141 strncpy(qr[i], ql[i], len1 - len2); 142 strcat(qr[i], tmp); 143 scanf("%s", bplan[i]); 144 if (strcmp(bplan[i], "invalid")) isvalid[i] = 1; 145 num[i] = i; 146 for (int j = 1; j < i; j++) 147 { 148 if (strcmp(bplan[j], bplan[i]) == 0) 149 { 150 num[i] = j; 151 break; 152 } 153 } 154 } 155 for (int i = n; i >= 1; i--) 156 { 157 string s = bplan[i]; 158 tree.Insert(ql[i], qr[i],num[i]); 159 } 160 tree.DFS(0); 161 printf("%d\n", tree.ans); 162 tree.Print(0, 0); 163 Case++; 164 } 165 return 0; 166 }
出处:http://www.cnblogs.com/ivan-count/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。