洛谷 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;
}

End

posted @ 2021-08-06 17:19  xixike  阅读(41)  评论(0编辑  收藏  举报