题意:
给定一个长度不超过30000的字符串 S ,然后给定n(n<=4000)个长度不超过100的字符串
ai ,问用 ai 组合成 S 有多少种方案数,由于数量可能很大,最终结果mod 20071027。
题解:
本来想学习一下字典树的,但是这题用到了dp。
dp[i] 表示从 i 开始到S 末尾有多少种组合方案。
那么dp公式为: dp[i]=∑dp[i+len(x)] (x表示已经存在的字符串)。
所以我们可以从后往前递推,枚举 dp[i] ,然后再枚举中断点判断 [i,j] 的单词是否出现过,最后 dp[0] 就是最终答案。但是如果直接暴力,会超时,所以我们需要用字典树保存 ai ,每次查找单词是否出现就在字典树中查询,字典树查询的复杂的就是O(h),h为字符串的长度。
字典树:
向字典那样讲字符串保存在树当中,其中ch[i][j]表示在i状态下,输入字符j时,下个状态的位置是多少(类似于邻接表);val[i]表示i状态的时候是否是一个字符串的终点(也可以记录在这个位置终结的字符串的个数)。其中ch[i][j]中j范围为字符集范围,例如小写字母是26个,0<=j<26;i比较大,最坏的情况下为 len(str)∗max{len(ai)} ,这样很可能超出内存,不过这种情况基本不会出现,如果内存不够可以开更小的数组或者改用指针形式的字典树。
my code
#pragma comment(linker, "/STACK:102400002,102400000")
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MOD = 20071027;
const int MAXN = 4005 * 100 + 100;
int ch[MAXN][26];
int val[MAXN];
int sz;
struct Trie {
Trie() {sz = 1; memset(ch[0], 0, sizeof(ch)); }
int idx(char c) { return c - 'a'; }
void insert(char *s, int v = 1) {
int u = 0;
while(*s) {
int c = idx(*s);
if(!ch[u][c]) {
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
*s++;
}
val[u] = v;
}
bool find(char* s, int len) {
int u = 0;
for(int i = 0; i <= len; i++) {
int c = idx(s[i]);
if(!ch[u][c])
return false;
u = ch[u][c];
}
return val[u];
}
};
char s[300005], word[105];
ll dp[300005];
int n;
int main() {
int cas = 1;
while(scanf("%s", s) != EOF) {
Trie trie;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%s", word);
trie.insert(word);
}
memset(dp, 0, sizeof(dp));
int len = strlen(s);
dp[len] = 1;
for(int i = len-1; i >= 0; i--) {
for(int j = 0; j < 100 && i + j < len; j++) {
if(trie.find(s + i, j)) {
dp[i] = (dp[i+j+1] + dp[i]) % MOD;
}
}
}
printf("Case %d: %lld\n", cas++, dp[0]);
}
return 0;
}