LA 3942 - Remember the Word (字典树 + dp)
题目大意:
给定一个字符串和给定一个单词集合。问从给定单词集合中选取单词,有多少种选取方法刚好拼接成字符串。
例如:
abcd
4
a
b
cd
ab
有两种
a-b-cd
ab-cd
这两种情况
解题思路:
因为给定的字符串的长度是3*10^5所以暴力就不能解决问题了。
dp[j + 1] = dp[j + 1] + dp[i];dp[j + 1]表示从S[0~j]有多少中组成方法 ,用公式的条件是S[i~j]是单词集合里的元素
有了上面的dp,时间减少了,但是每次查找单词如果枚举每个单词,则时间复杂度3*10^5*4*10^3*10^2。我们用字典树
存储所有单词。每次查找单词时间最坏是O(10^2)。
所以总时间复杂度是O(n*10^2)。
参开资料《算法入门经典训练之南》刘汝佳 P209
AC代码:
1 #include<cstdio> 2 #include<cstring> 3 4 #define STR 300000 + 10//模板串的长度 5 #define SIZE 400000 //字典树的节点数 6 #define LETTER 26 //字符个数 7 #define S 100+10//单词的长度 8 #define MOD 20071027 9 10 int size, trie[SIZE][LETTER];//size字典的节点数 trie字典树的节点 11 bool val[SIZE];//记录字典树的节点是否为单词 12 char strp[STR];//模板单词 13 int dp[STR];//dp[i]表示从0~i有多少种组成模板单词 14 15 void init(int x){//初始化节点 16 val[x] = 0; 17 memset(trie[x], 0, sizeof(trie[x])); 18 } 19 20 int idx(char c){ 21 return c - 'a'; 22 } 23 24 void insert(char str[]){ 25 int u = 0; 26 for(int i = 0; str[i]; ++i){ 27 int num = idx(str[i]); 28 if(!trie[u][num]){//儿子为空 29 init(size);//扩充节点 30 trie[u][num] = size++; 31 } 32 u = trie[u][num];//指向下一个节点 33 } 34 val[u] = true;//当前节点是一个单词的末尾 35 } 36 37 void dynamic(int cs){ 38 memset(dp, 0, sizeof(dp)); 39 dp[0] = 1; 40 int i; 41 for(i = 0; strp[i]; ++i){ 42 int u = 0; 43 for(int j = i; strp[j]; ++j){ 44 int num = idx(strp[j]); 45 if(!trie[u][num]){ 46 break; 47 } 48 u = trie[u][num]; 49 if(val[u]){ 50 dp[j + 1] = (dp[j + 1] + dp[i]) % MOD;//dp公式 51 } 52 } 53 } 54 printf("Case %d: %d\n", cs, dp[i]); 55 } 56 57 int main(){ 58 int s; 59 char str[S]; 60 for(int cs = 1; ~scanf("%s", strp); ++cs){ 61 62 init(0);//初始化节字典树 63 size = 1; 64 65 scanf("%d", &s); 66 while(s--){ 67 scanf("%s", str); 68 insert(str); 69 } 70 dynamic(cs); 71 } 72 return 0; 73 }