P2292 [HNOI2004] L 语言 题解 AC自动机 + 状态压缩 + dp
题目链接:https://www.luogu.com.cn/problem/P2292
题目大意:
给定 \(n(\le 20)\) 个模式串 \(s_i(|s_i| \le 20)\),有 \(m(\le 50)\) 次询问,每次询问给出一个字符串 \(t(|t| \le 10^6)\)。
对于每次询问的字符串 \(t\),你需要回答出它的由模式串拼成(模式串可以重复使用)的最长前缀的长度。
解题思路:
80分
用 \(f_i\) 表示能不能匹配到第 \(i\) 个位置,然后枚举 \(t\) 的每一个位置,跑一遍 AC自动机进行匹配。
时间复杂度 \(O(mST)\)(其中 \(S\) 表示 \(s_i\) 的最大长度;\(T\) 表示 \(t\) 的最大长度)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
struct Node {
int son[26], fail;
bool flag;
Node() {
memset(son, 0, sizeof(son));
fail = 0;
flag = false;
}
} tr[maxn];
int n, m, cnt;
char s[22][22], t[maxn];
bool f[maxn];
void ins(char *s) {
int u = 0;
for (; *s; s++) {
int c = (*s) - 'a';
if (!tr[u].son[c])
tr[ tr[u].son[c] = ++cnt ] = Node();
u = tr[u].son[c];
}
tr[u].flag = true;
}
void build() {
queue<int> que;
for (int i = 0; i < 26; i++)
if (tr[0].son[i])
que.push(tr[0].son[i]);
while (!que.empty()) {
int u = que.front();
que.pop();
for (int i = 0; i < 26; i++) {
int v = tr[u].son[i];
if (!v) continue;
int x = tr[u].fail;
while (x && !tr[x].son[i])
x = tr[x].fail;
tr[v].fail = tr[x].son[i];
que.push(v);
}
}
}
bool vis[maxn];
void cal(char *s, int p) {
int u = 0;
for (int i = p; *s; s++, i++) {
int c = (*s) - 'a';
if (tr[u].son[c])
u = tr[u].son[c];
else
break;
f[i] |= tr[u].flag;
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%s", s[i]);
ins(s[i]);
}
for (int i = 0; i < m; i++) {
scanf("%s", t);
int len = strlen(t);
memset(f, 0, sizeof(bool) * (len+1));
for (int j = 0; j < len; j++) {
if (j && !f[j-1])
continue;
cal(t+j, j);
}
int ans = 0;
for (int i = len-1; i >= 0; i--) {
if (f[i]) {
ans = i+1;
break;
}
}
printf("%d\n", ans);
}
return 0;
}
100分
考虑在字典树每一个节点维护一个二进制状态,状态的第 \(i\) 位为 \(1\) 表示存在一个长度为 \(i\) 的以当前节点结尾的后缀。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 5;
struct Node {
int son[26], fail, d, s;
bool flag;
Node() {d = 0;}
Node(int _d) {
d = _d;
s = 0;
memset(son, 0, sizeof(son));
fail = 0;
flag = false;
}
} tr[100010];
int n, m, cnt;
char s[22], t[maxn];
bool f[maxn];
void ins(char *s) {
int u = 0;
for (int d = 1; *s; d++, s++) {
assert(d <= 20);
int c = (*s) - 'a';
if (!tr[u].son[c])
tr[ tr[u].son[c] = ++cnt ] = Node(d);
u = tr[u].son[c];
}
tr[u].s = 1 << tr[u].d;
tr[u].flag = true;
}
void build() {
queue<int> que;
for (int i = 0; i < 26; i++)
if (tr[0].son[i])
que.push(tr[0].son[i]);
while (!que.empty()) {
int u = que.front();
que.pop();
for (int i = 0; i < 26; i++) {
int v = tr[u].son[i];
if (!v) continue;
int x = tr[u].fail;
while (x && !tr[x].son[i])
x = tr[x].fail;
tr[v].fail = tr[x].son[i];
que.push(v);
}
}
}
void bfs() {
queue<int> que;
que.push(0);
while (!que.empty()) {
int u = que.front();
que.pop();
// for (int v = tr[u].fail; v; v = tr[v].fail)
// if (tr[v].flag)
// tr[u].s |= 1 << tr[v].d;
int v = tr[u].fail;
tr[u].s |= tr[v].s;
for (int i = 0; i < 26; i++) {
int v = tr[u].son[i];
if (v) que.push(v);
}
}
}
int main() {
tr[0] = Node(0);
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%s", s);
ins(s);
}
build();
bfs();
while (m--) {
scanf("%s", t + 1);
int len = strlen(t + 1);
memset(f, 0, sizeof(bool) * (len+1));
f[0] = true;
int ans = 0, tmp = 1;
for (int i = 1, u = 0; i <= len; i++) {
int c = t[i] - 'a';
while (u && !tr[u].son[c])
u = tr[u].fail;
if (tr[u].son[c])
u = tr[u].son[c];
tmp <<= 1;
f[i] = tmp & tr[u].s;
if (f[i]) {
ans = i;
tmp |= 1;
}
}
printf("%d\n", ans);
}
return 0;
}