Trie字符串(Remember the Word, LA 3942)
給出一個由S個不同單詞組成的字典和一個長字符串。把這個字符串分解成若干個單詞的連接(單詞可重複使用),有多少種方法?比如,有4個單詞a、b、cd、ab,則abcd有兩種分解方法:a+b+cd和ab+cd。
輸入格式:
輸入包含多組數據。每組數據第一行爲小寫字母組成的待分解字符串,長度不超過300000.第二行爲單詞個數S(1<=S<=4000)。一下S行每行爲一個單詞,由不超過10個小寫字母組成。輸入結束標誌爲文件結束符(EOF)
輸出格式:
對於每組數據,輸出分解方案數除以20071027的餘數。
分析:
用遞推+trie字符串
1、令d[i]表示從字符i開始的字符串(即後綴S[i…L])的分解方案數,則d[i] = sum{d[i + len(x)]|單詞x是S[i…L]的前綴}。
如果先枚舉x,再判斷它是否爲S[i…L]的前綴,時間無法承受(最多可能有4000個單詞,判斷還需要一段時間)換個思路,先把所有單詞組織成Trie,然後試着在Trie中“查找”S[i…L].查找過程中每經過一個單詞結點,就找到一個上述狀態轉移方程中的x,最多隻需比較100次就能找到所有x。
//
// main.cpp
// Trie字符串
//
// Created by zhoujl on 15/10/29.
// Copyright (c) 2015年 zhoujl. All rights reserved.
//
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 4000*100+3;
const int sigma_size = 26;
struct Trie {
int ch[maxn][sigma_size];
int sz;
int val[maxn];
void clear() {
sz = 1;
val[0] = 0;
memset(ch[0], 0, sizeof(ch[0]));
}
int idx(char c) {
return c - 'a';
}
void insert(char * s, int v) {
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]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = v;
}
void find(const char * s, int len, vector<int>& ans) {
int u = 0;
for (int i = 0; i < len; i++) {
int c = idx(s[i]);
if (s[i] == '\0')
break;
if (!ch[u][c]) {
return;
}
u = ch[u][c];
if (val[u]) {
ans.push_back(val[u]);
}
}
}
};
const int MOD = 20071027;
const int maxl = 300000 + 10;
char text[maxl];
int d[4005];
char s[105];
Trie a;
int main() {
int S;
while (scanf("%s%d", text, &S) == 2) {
a.clear();
int len = strlen(text);
getchar();
memset(d, 0, sizeof(d));
for (int i = 0; i < S; i++) {
scanf("%s", s);
int v = strlen(s);
a.insert(s, v);
}
d[len] = 1;
for (int i = len-1; i >= 0; i--) {
vector<int> p;
a.find(text+i, len-i, p);
for(int j = 0; j < p.size(); j++)
d[i] = (d[i] + d[i+p[j]]) % MOD;
}
printf("%d", d[0]);
}
}