题解:LGP2292 [HNOI2004] L 语言
题目
给定 \(n(\le 20)\) 个字符串 \(s_i(|s_i|\le 20)\) 构成集合 \(A\),\(m(\le 50)\) 个字符串 \(t_i(|t_i|\le 2\times 10^6)\)。
定义一个字符串为合法,当且仅当该字符串可以划分成几个子串,使得每个子串都属于 \(A\)。
对每个 \(t\) 求出最长合法前缀的长度。
题解
Part 1. DP
设 \(f(i)\) 表示 \(t\) 的前缀 \([1,i]\) 是否合法,其中 \(f(0)=1\),于是,
\(f(i) = \max\limits_{j=1}^{n} f(i-|s_j|)\land [t[i-|s_j|+1,i]=s_j]\),
直接转移,再用哈希或者其他方法做到 \(O(1)\) 比较字符串,总复杂度 \(O(m|t|n)\)。
Part 2. AC自动机
考虑用 AC自动机 做多模式匹配的时候,我们从 \(1\) 到 \(n\) 遍历文本串 \(t\),然后对于每个 \(i\),从 \(i\) 不断跳 \(fail\) 指针(即 \(i \to fail_i\)),那么每次跳到的节点(假设存在这个点代表的单词),就表示 \(t\) 以 \(i\) 结尾存在这样一个子串。
那么先对 \(A\) 建出 AC自动机,然后转移 \(f\) 时,对于每个 \(i\),反复跳 \(fail\),和 Part 1. 一样的转移。
然而这样最坏复杂度和上面一样。
Part 3. 状态压缩
对于每个 \(i\),我们想知道的是 \(i\) 对应的 \(fail\) 链上存在的所有单词的长度有哪些,注意到 \(|s|\le 20\),那么可以状压这条链的信息,用 \(2^x\) 表示该链上长度为 \(x\) 的单词是否存在。
这个可以在求 \(fail\) 的 BFS 时处理,也就是说节点 \(u\) 上的状压信息储存的是 \(u\) 到根的 \(fail\) 链上的信息。
并且每个 \(f(i)\) 最多只会由前 \(20\) 个 \(f(j)\) 转移过来,那么我们便可以状压前 \(20\) 个 \(f\) 的信息,每次通过左移来"滚动数组"。
具体来说,设 \(g\) 的第 \(x\) 位(从 \(0\) 开始)表示 \(f(i-x)\) 的状态。记我们在 \(fail\) 链上的信息为 \(h\),那么每次只需要判断 \(h \& g\) 是否为 \(0\) 即可,如果不为 \(0\) 就表示 \(f(i)=1\),那么将 \(g\) 的第 \(0\) 位设为 \(1\)。然后 \(i+1\) 的同时 \(g\) 左移一位即可。
\(g\) 可以用 unsigned int,方便滚动。
复杂度 \(O(m|t|)\)。
代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
const int N = 405;
int n, m;
string s, t;
int trie[N][26], cnt[N], fail[N], sta[N], dep[N], idx;
void insert(string s) {
int cur = 0;
for (char c : s) {
if (!trie[cur][c - 'a']) trie[cur][c - 'a'] = ++idx;
cur = trie[cur][c - 'a'];
}
cnt[cur]++;
}
void build() {
queue<int> q;
for (int i = 0; i <= 25; i++) {
if (trie[0][i]) {
q.push(trie[0][i]);
dep[trie[0][i]] = 1;
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
sta[u] = sta[fail[u]];
if (cnt[u]) sta[u] |= 1 << dep[u];
for (int i = 0; i <= 25; i++) {
if (trie[u][i]) {
fail[trie[u][i]] = trie[fail[u]][i];
q.push(trie[u][i]);
dep[trie[u][i]] = dep[u] + 1;
} else {
trie[u][i] = trie[fail[u]][i];
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s;
insert(s);
}
build();
while (m--) {
cin >> t;
unsigned int f = 1;
int cur = 0, ans = 0;
for (int i = 0; i < t.size(); i++) {
cur = trie[cur][t[i] - 'a'];
f <<= 1;
if (sta[cur] & f) {
ans = i + 1;
f |= 1;
}
}
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号