### Problem Statement You are given $N$ lowercase...
创建于:2025年8月16日
使用 GPT-5 Thinking 回答 Chat01
创建于:2025年8月16日
使用 GPT-5 Thinking 回答 Chat01
You are given lowercase English strings , and an integer .
Find the number, modulo , of length- lowercase English strings that contain all of as substrings.
What is a substring? A substring of is a string obtained by deleting zero or more characters from the beginning and zero or more characters from the end of .
For example, ab
, bc
, and bcd
are substrings of abcd
, while ac
, dc
, and e
are not substrings of abcd
.
The input is given from Standard Input in the following format:
text$N$ $L$ $S_1$ $S_2$ $\vdots$ $S_N$
Output the answer.
text2 4 ab c
153
Some of the strings that satisfy the condition are abcz
and cabc
. acbd
does not contain ab
as a substring, so it does not satisfy the condition.
text2 6 abc cde
54
text5 50 bbfogggj zkbach eedirhyc ffgd oemmswj
689020583
中文c++回答。
下面给出一种标准做法:用 Aho–Corasick 自动机统计“匹配到哪些模式串”的状态,并在长度为 的位置上做 DP。
设自动机有 K
个节点、模式数为 N(≤8)
,用一个 mask
(0…)表示已经出现过的模式集合。转移时对 26 个字母走自动机边,并把到达节点携带的输出位并入 mask
。在长度 处、mask == (1<<N)-1
的所有状态之和即为答案。取模 。
时间复杂度约为 ,这里 ,因此完全可行。
注意:若某个模式是另一个模式的子串,AC 自动机的失败链接与输出集合会自然处理,无需手动去重。
cpp#include <bits/stdc++.h> using namespace std; static const int MOD = 998244353; struct AC { struct Node { int nxt[26]; int fail; int outMask; // 该节点(及其通过fail传播后)代表出现了哪些模式 Node() : fail(0), outMask(0) { fill(nxt, nxt + 26, -1); } }; vector<Node> t; AC() { t.emplace_back(); } // root = 0 void insert(const string &s, int id) { int u = 0; for (char ch : s) { int c = ch - 'a'; if (t[u].nxt[c] == -1) { t[u].nxt[c] = (int)t.size(); t.emplace_back(); } u = t[u].nxt[c]; } t[u].outMask |= (1 << id); } void build() { queue<int> q; // 初始化 root 的 26 条“自动转移” for (int c = 0; c < 26; ++c) { int v = t[0].nxt[c]; if (v == -1) t[0].nxt[c] = 0; else { t[v].fail = 0; q.push(v); } } // BFS 构建 fail,并把输出集合沿 fail 传播 while (!q.empty()) { int u = q.front(); q.pop(); t[u].outMask |= t[t[u].fail].outMask; for (int c = 0; c < 26; ++c) { int v = t[u].nxt[c]; if (v == -1) t[u].nxt[c] = t[t[u].fail].nxt[c]; else { t[v].fail = t[t[u].fail].nxt[c]; q.push(v); } } } } }; int addmod(int a, int b) { a += b; if (a >= MOD) a -= MOD; return a; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, L; if (!(cin >> N >> L)) return 0; vector<string> S(N); for (int i = 0; i < N; ++i) cin >> S[i]; AC ac; for (int i = 0; i < N; ++i) ac.insert(S[i], i); ac.build(); int K = (int)ac.t.size(); int M = 1 << N; int FULL = M - 1; // 滚动数组 dp[pos%2][state * M + mask] vector<int> dp0(K * M, 0), dp1(K * M, 0); dp0[0] = 1; // 长度0,处于root、mask=0 for (int pos = 0; pos < L; ++pos) { fill(dp1.begin(), dp1.end(), 0); for (int st = 0; st < K; ++st) { int base = st * M; for (int mask = 0; mask < M; ++mask) { int cur = dp0[base + mask]; if (!cur) continue; // 尝试 26 个字母 for (int c = 0; c < 26; ++c) { int ns = ac.t[st].nxt[c]; int nmask = mask | ac.t[ns].outMask; int idx = ns * M + nmask; dp1[idx] = addmod(dp1[idx], cur); } } } dp0.swap(dp1); } int ans = 0; for (int st = 0; st < K; ++st) { ans = addmod(ans, dp0[st * M + FULL]); } cout << ans << '\n'; return 0; }
outMask
确保任意匹配均被记录。dp[pos][state][mask]
只保留两层(滚动),节省内存。mask==FULL
的所有状态的计数和。