S。S ≤ 4000 個單詞,每個長度 ≤ 100,全小寫,無重複。S 拆分為字典單詞序列的方案數,模 20071027。dp[i]:從位置 i(0-indexed)開始到字符串末尾的合法拆分方案數。
答案 = dp[0]。
邊界:dp[n] = 1(空串 1 種拆分)。
從後向前枚舉 i = n-1 .. 0:
S[i], S[i+1], ... 遍歷。u,若 u 是單詞結尾(val[u] > 0),則 dp[i] += val[u] * dp[j+1],其中 j 為當前字符索引。struct Node {
int son[26] = {0};
int val = 0; // 以此節點結尾的單詞數(本題無重複單詞,故 0/1)
} nodes[MAX_NODES]; // MAX_NODES ≈ 4000 * 100 = 400,000
#include
using namespace std;
const int MOD = 20071027;
const int MAXN = 300005;
const int MAXNODE = 400005;
int son[MAXNODE][26], val[MAXNODE], dp[MAXN], nodeCnt;
char S[MAXN], word[105];
void insertWord(const char* s) {
int u = 0;
for (int i = 0; s[i]; ++i) {
int c = s[i] - 'a';
if (!son[u][c]) {
son[u][c] = ++nodeCnt;
memset(son[nodeCnt], 0, sizeof(son[nodeCnt]));
val[nodeCnt] = 0;
}
u = son[u][c];
}
val[u] = 1; // 標記單詞結尾
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int caseNo = 1;
while (scanf("%s", S) == 1) {
int n = strlen(S);
int dictSize; scanf("%d", &dictSize);
// 重置 Trie
nodeCnt = 0;
memset(son[0], 0, sizeof(son[0]));
val[0] = 0;
// 讀入字典
for (int i = 0; i < dictSize; ++i) {
scanf("%s", word);
insertWord(word);
}
// DP
dp[n] = 1;
for (int i = n - 1; i >= 0; --i) {
int u = 0;
dp[i] = 0;
for (int j = i; j < n; ++j) {
int c = S[j] - 'a';
if (!son[u][c]) break;
u = son[u][c];
if (val[u]) {
dp[i] = (dp[i] + 1LL * val[u] * dp[j+1]) % MOD;
}
}
}
printf("Case %d: %d\n", caseNo++, dp[0]);
}
return 0;
}
son[MAXNODE][26] 直接訪問極快。dp[i],依賴 dp[j+1] (j>i) 已計算。break,避免無效遍歷。1LL * val * dp)。scanf 讀字符串,避免 cin 慢。O(總字典字符數) ≤ 4×10⁵。i 向後匹配直到 Trie 斷開,最長 100 步。總 O(n × 最大單詞長) ≤ 3×10⁷。4×10⁵ × 26 × 4B ≈ 40 MB,DP 3×10⁵ × 4B ≈ 1.2 MB,總 < 64 MB。UVA 1401 是「Trie 加速字典匹配 + 線性 DP」的經典組合。關鍵在於: