【模板】AC自动机(二次加强版)

91 篇文章 0 订阅
10 篇文章 0 订阅

【模板】AC自动机(二次加强版)

题目链接:luogu P5357

题目大意

给出一些字符串和一个大字符串,求每一个字符串在大字符串中出现的次数。

思路

如果没有做普通加强版的可以先点我这里看看。

这道题跟前面有一点相像:反正我们上一题其实也求出了每个字符串的出现次数。
但是我们可以发现在这个数据规模下遇到特殊情况会超时,而且我的代码还会 MLE。

MLE 的我们可以改成之前我在最后说的方法来解决(就是把相同的合成一个,然后最后在分开来),但是 TLE 的怎么解决呢?
(在这里说一下会 TLE 的数据,就是都是相同的字符,然后 fail 就只能一个一个跳,就会超时)

我们想一下以某一个位置结尾时,计算中是怎么计算的。
就是沿着 fail 边找到那一条 fail 边组成的链,然后把链上的点都加一次。

那我们怎么让这个操作由找链时的 O(n) 变成更低的呢?
可以想到在这个 “fail 树” 上做前缀和。

那我们怎么还原呢?
就是要跑一遍图,然后某个点加上能跳到他的点的确定最终值。

但是怎么保证你每次找到的点都已经可以加了呢?
可以想到用拓扑序。

然后就实现,就可以了。

代码

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

using namespace std;

struct node {
	int num, fail, kind;
	int son[31];
}tree[2000001];
int n, size, now, thi, KK, ru[2000001], pla[2000001], real[2000001];
queue <int> q;
char c[2000001];

void build_Trie(int which) {
	now = 0;
	size = strlen(c);
	for (int i = 0; i < size; i++) {
		thi = c[i] - 'a';
		if (!tree[now].son[thi]) tree[now].son[thi] = ++KK;
		now = tree[now].son[thi];
	}
	
	if (!tree[now].kind) tree[now].kind = which;
	pla[which] = tree[now].kind;
}

void get_fail() {
	for (int i = 0; i < 26; i++)
		if (tree[0].son[i]) {
			q.push(tree[0].son[i]);
			tree[tree[0].son[i]].fail = 0;
			ru[tree[tree[0].son[i]].fail]++;
		}
	
	while (!q.empty()) {
		now = q.front();
		q.pop();
		
		for (int i = 0; i < 26; i++) {
			if (tree[now].son[i]) {
				tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
				q.push(tree[now].son[i]);
				ru[tree[tree[now].son[i]].fail]++;
			}
			else tree[now].son[i] = tree[tree[now].fail].son[i];
		}
	}
}

void get_AC() {
	size = strlen(c);
	now = 0;
	
	for (int i = 0; i < size; i++) {
		thi = c[i] - 'a';
		now = tree[now].son[thi];
		tree[now].num++;
	}
}

void tuopu() {//利用拓扑,实现前缀和的还原
	for (int i = 0; i <= KK; i++)
		if (!ru[i])
			q.push(i);
	
	while (!q.empty()) {
		now = q.front();
		q.pop();
		
		real[tree[now].kind] = tree[now].num;
		ru[tree[now].fail]--;
		if (!ru[tree[now].fail]) {
			q.push(tree[now].fail);
		}
		tree[tree[now].fail].num += tree[now].num;
	}
}

int main() {
	scanf("%d", &n);
	
	for (int i = 1; i <= n; i++) {
		scanf("%s", &c);
		build_Trie(i);
	}
	
	get_fail();
	
	scanf("%s", &c);
	get_AC();
	
	tuopu();
	
	for (int i = 1; i <= n; i++)
		printf("%d\n", real[pla[i]]);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值