洛谷 P2292 [HNOI2004]L语言
洛谷 P2292 [HNOI2004]L语言
Solution
AC自动机
这题一眼看上去就是道 \(AC\) 自动机题。
于是快速地把 \(AC\) 自动机板子打出来,并建好 \(trie\) 图。
接下来分析一下题目
我们用 \(vis[i]\) 标记一段文章长度为 \(i\) 的前缀是否可以被表示出来。
对于一段文章,我们枚举它的前缀,设长度为 \(i\),然后暴力跳 \(fail\) 指针,每次判断 \(i - End[res] + 1\) 是否能够被字典中的单词表示出来。
-
\(End[i]:\) 以 \(i\) 结尾的单词的长度
-
\(res:\) 暴力跳前缀的 \(fail\) 指向的点的编号
判断时要 \(+1\) 是因为我们要输出长度,而字符串是从第 0 为开始的,所以 \(+1\)。
结合代码理解一下吧
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N = 2e6 + 10;
int n, m;
char s[13], t[N];
int End[N];
int trie[300][27], tot, fail[N];
bool vis[N];
//--------------------------------------------AC自动机模板不解释
void insert(char s[]){
int len = strlen(s);
int now = 0;
for(int i = 0; i < len; i++){
int x = s[i] - 'a';
if(!trie[now][x]) trie[now][x] = ++tot;
now = trie[now][x];
}
End[now] = len;
}
void build(){
queue <int> q;
for(int i = 0; i < 26; i++)
if(trie[0][i])
q.push(trie[0][i]);
while(!q.empty()){
int now = q.front();
q.pop();
for(int i = 0; i < 26; i++){
if(trie[now][i]){
fail[trie[now][i]] = trie[fail[now]][i];
q.push(trie[now][i]);
}else trie[now][i] = trie[fail[now]][i];
}
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%s", s);
insert(s);
}
build();
while(m--){
memset(vis, 0, sizeof(vis));
vis[0] = 1; //长度为0的前缀可以被表示出来
int ans = 0, now = 0;
scanf("%s", t);
for(int i = 0; i < strlen(t); i++){
now = trie[now][t[i] - 'a'];
int res = now; //res转存一下
while(res){
if(vis[i - End[res] + 1]){ //判断i - End[res] + 1能否被表示
vis[i + 1] = 1;
break;
}
res = fail[res]; //暴力跳fail
}
if(vis[i + 1])
ans = i + 1;
}
printf("%d\n", ans);
}
return 0;
}