【ybt高效进阶2-5-3】前缀匹配

前缀匹配

题目链接:ybt高效进阶2-5-3

题目大意

有一个大串和一些小串,问你每个小串的前缀在大串上的最长匹配长度是多少。
每个串只有 E,S,W,N 四个字符组成。

思路

由于我们看到字符串长度都很大,我们考虑不是把 AC 自动机和其他东西结合,而是把 AC 自动机进行变形。

那怎么变形呢?
我们可以枚举每个小串的每个前缀,然后询问是否在大串中出现,那就是要 O ( 1 ) O(1) O(1) 查询。

那怎么弄呢?我们考虑预处理出来。
那我们原本是查询每个小串是否在大串中出现,现在我们如果建出每个小串组成的 Trie 树,然后枚举大串的前缀,然后看在 Trie 树上的那些地方还有这个串(也就是跳 fail 边),那你找到的串都是大串的子串。

然后就好了。

代码

#include<queue>
#include<cstdio>
#include<cstring>

using namespace std;

struct Trie {
	int son[4], fail;
	bool in;
}tree[10000001];
int n, m, ans[100001], tot;
char s[10000001], c[100001][101];
queue <int> q;

int get_go(char c) {//给每个字符给一个独特的位置,减少空间
	if (c == 'E') return 0;
	if (c == 'S') return 1;
	if (c == 'W') return 2;
	if (c == 'N') return 3;
	return -1;//读入的时候这四个都不是,就要重新读入
}

void insert(int op) {//AC 自动机插入串
	int size = strlen(c[op]);
	int now = 0;
	for (int i = 0; i < size; i++) {
		int go = get_go(c[op][i]);
		if (!tree[now].son[go]) tree[now].son[go] = ++tot;
		now = tree[now].son[go];
	}
}

void build_fail() {//建 Trie 树上的 fail 边
	for (int i = 0; i <= 3; i++)
		if (tree[0].son[i]) {
			q.push(tree[0].son[i]);
			tree[tree[0].son[i]].fail = 0;
		}
	
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		
		for (int i = 0; i <= 3; i++)
			if (tree[now].son[i]) {
				q.push(tree[now].son[i]);
				tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
			}
			else tree[now].son[i] = tree[tree[now].fail].son[i];
	}
}

void add() {//给这个大串查询
	int now = 0;
	for (int i = 1; i <= n; i++) {//枚举它的前缀
		int go = get_go(s[i]);
		int noww = tree[now].son[go];
		while (noww) {//找到 Trie 树上所有的这个串
			tree[noww].in = 1;
			noww = tree[noww].fail;
		}
		now = tree[now].son[go];
	}
}

int ask(int op) {
	int size = strlen(c[op]);
	int now = 0, re = 0;
	for (int i = 0; i < size; i++) {//询问每个前缀是否存在于 Trie 数中
		int go = get_go(c[op][i]);
		now = tree[now].son[go];
		if (tree[now].in) re = i + 1;//存在,要找最长距离的
	}
	return re;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		s[i] = getchar();
		while (get_go(s[i]) == -1) s[i] = getchar();
	}
	
	for (int i = 1; i <= m; i++) {
		scanf("%s", &c[i]);
		insert(i);
	}
	
	build_fail();
	
	add();
	
	for (int i = 1; i <= m; i++) {
		printf("%d\n", ask(i));
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值