题意:给你一个由s个不同单词组成的字典和一个长字符串L,让你把这个长字符串分解成若干个单词连接(单词是可以重复使用的),求有多少种。(算法入门训练指南-P209)
析:我个去,一看这不是一个DP吗?刚开始交一直是runtime error,找了好久,一直以为是数组开小了,不断增大还是这样,后来发现我用了char类型。。。下面分析这个题目
应该不难想到这个状态转移方程:
d(i) = sum{d(i+len(x))|单词x是s[i...L]的前缀},其中len(x)是长度。d(i)表示从字符i开始的字符串(也就是后缀s[i...L])的种数。
很明显我们是从后往前递推的,d(i)的种数应该是由d(i)和d(i+len(x))的和组成的(想想为什么,不理解可以画个图分析一下)。
如果先枚举x,再判断它是不是s[i...L]的前缀,时间复杂度太高了。所以换一个思路,先把单词组成Tire(前缀树),然后试着在Tire中去找s[i..L]。具体参考代码。
代码如下:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 4000 * 100 + 10; // 4000个单词,每个单词最长是100,最多就有这么多 const int maxm = 300010; const int mod = 20071027; int d[maxm]; char ss[maxm], t[110]; struct Tire{ int ch[maxn][26]; int val[maxn]; int sz; void init() { sz = 1; memset(val, 0, sizeof(val)); memset(ch[0], 0, sizeof(ch[0])); } //初始化 int idx(char c){ return c - ‘a‘; } //获得编号 void inser(char *s){ // 插入 int u = 0, n = strlen(s); for(int i = 0; i < n; ++i){ int c = idx(s[i]); if(!ch[u][c]){ memset(ch[sz], 0, sizeof(ch[sz])); ch[u][c] = sz++; } u = ch[u][c]; } val[u] = n; } void quary(char *s, int i, int n){ // 查找 int u = 0; for(int j = 0; j < n; ++j){ int c = idx(s[j]); if(!ch[u][c]) return ; u = ch[u][c]; if(val[u]) d[i] = (d[i] + d[i + val[u]]) % mod; } } }; Tire tire; int main(){ int n, kase = 0; while(~scanf("%s %d", ss, &n)){ tire.init(); //一定要初始化,刚开始Tire定义在里面,一运行就崩。。。我也是醉了 for(int i = 0; i < n; ++i){ scanf("%s", t); tire.inser(t); } memset(d, 0, sizeof(d)); int len = strlen(ss); d[len] = 1;// 这个地方是边界,注意初始化 for(int i = len-1; i >= 0; --i) tire.quary(ss+i, i, len-i); //递推种数 printf("Case %d: %d\n", ++kase, d[0]); } return 0; }
时间: 2024-10-12 03:56:09