题意:给出一个字符串和S个单词组成的字典,问把这个字符串分解成若干单词的连接,总共有多少种?(单词可重复)
解析:这是刘汝佳白皮书的关于Trie树的入门,也是我接触的第一题。总的思路就是可以用递推来求到底有多少种分解方法。假如用d[i]表示从第i个字符开始往后的后缀字符串的分解方法,即s[i]->s[len] (len为字符串长度)之间的字符串。
那么假如说在s[i...len]的字符串中发现s[i...i+n]是一个字典中的单词,那么d[i] = d[i] + d[i+n];同理的话d[i] = sum{ d[i+len(x)] },x是s[i...len] 的一个前缀单词。
那么我们不可能枚举所有的单词x来判断他是否是s[i...len]的一个前缀,那样必然超时,所以我们就把字典中的单词全部都存到Trie树中,在树中查找单词。
关于Trie树,我们对所有的节点全都从1到n标上序号,其中0为根节点,那么总共有sz个节点。然后因为字符集只是小写字母,所以我们用ch[i][0] != 0表示节点i有个子节点是字符'a',ch[i][1] != 0表示节点i有个子节点为'b',ch[i][2] = 0表示节点i没有'c'的子节点.
其中Trie树还有个附加信息,存在val[]中,如果该点是单词节点的话,val[]中存储的就是该点的该单词的长度,否则val[]为0
#include"stdio.h" #include"string.h" #include"vector" using namespace std; #define maxnode 100*4000+10 //跟sz的大小相同 #define sigma_size 27 #define Mod 20071027 vector<int>ans; //用来存储字符串后缀中所存在单词的长度 struct Trie{ int ch[maxnode][sigma_size]; //用来存储Trie树 int val[maxnode]; //树节点的附加信息 int sz; //整棵树节点的总数 void init(){ sz = 1; //初始只有一个根节点,编号为0 memset(ch[0],0,sizeof(ch[0])); } int idx(char c){ return c - 'a'; //对于小写字母集,获得c的编号 } //插入字符串s,v代表的是附加信息。如果v为0代表本节点不是单词节点,所以v必须非0 void insert(char *s,int v){ int u = 0,len = strlen(s); for(int i = 0 ; i < len ; i ++){ int c = idx(s[i]); if(!ch[u][c]){ //如果该节点不存在 memset(ch[sz],0,sizeof(ch[sz])); val[sz] = 0; //中间节点,不是单词节点,附加信息为0 ch[u][c] = sz++; //新建节点 } u = ch[u][c]; //到下一节点 } val[u] = v; //字符串最后一个必然是单词节点 } //查询从指针s开始长度为len的字符串是否存在 void find(char *s , int len){ int u = 0; //int len = strlen(s); 每次都求长度的话会超时,所以找规律 ans.clear(); for(int i = 0 ; i < len ; i++){ int c = idx(s[i]); if(!ch[u][c]) return ; //不存在该字符串 u = ch[u][c]; if(val[u]) //如果发现该节点是单词节点,就把该单词长度存入ans ans.push_back(val[u]); } } }t; int d[300010]; char str[300010],temp[110]; int n; void solve(){ memset(d,0,sizeof(d)); int len = strlen(str); d[len] = 1; //给予一个初始值 for(int i = len - 1; i >= 0 ; i--){ t.find(str+i,len-i); for(int j = 0 ; j < ans.size(); j++){ d[i] = (d[i]+d[i+ans[j]])%Mod; } } printf("%d\n",d[0]); } int main(){ memset(str,0,sizeof(str)); int Case = 1; while(scanf("%s",str) != EOF){ scanf("%d",&n); t.init(); for(int i = 0 ; i < n ; i ++){ scanf("%s",temp); int len = strlen(temp); t.insert(temp,len); //单词节点储存的信息就是该单词的长度 } printf("Case %d: ",Case++); solve(); } return 0; }