首页 > 其他分享 >KMP 自动机,孤独的自动机(同时也是CF1721E的题解)

KMP 自动机,孤独的自动机(同时也是CF1721E的题解)

时间:2022-11-06 14:33:06浏览次数:72  
标签:字符 CF1721E ch int 题解 AC KMP 自动机

  • 给定字符串 \(s\),以及 \(q\) 个串 \(t_i\),求将 \(s\) 分别与每个 \(t_i\) 拼接起来后,最靠右的 \(|t_i|\) 个前缀的 border 长度。询问间相互独立。

  • \(|s|\leq 10^6, q \leq 10^5, |t_i|\leq 10\) 。

(题面来自洛谷)


看到 border ,第一反应就是 KMP 。又想到 KMP 的 next 数组就是干这件事的,于是就有了一个很逊的想法:每次当做 \(t\) 真的接到了 \(s\) 的后面,对 \(t\) 的字符做 KMP 。看起来复杂度很对,但由于要往回跳,不出七个测试点就会超时。

思考一下,如果跳到了 s 以内的字符,那么实际上跳到哪里是固定的。由于我们只需要对 t 中字符匹配,所以完全可以只存储跳到 s 中每个字符时最终会跳到哪里。

发现 AC 自动机就是干这件事的,于是我们可以对单个字符串建立 AC 自动机。无聊的人类将单个串的 AC 自动机叫做 KMP 自动机。

很棒的一点是由于 AC 自动机单个字符的构建是 \(O(1)\) 的,所以我们可以直接按照上面的很逊的方式构建 AC 自动机。预处理复杂度为 \(O(|S|)\) ,单词询问复杂度为\(O(|T|)\)。

这个优化的本质是将 KMP 中往回跳的路径用 AC 自动机的构造方式进行压缩。

要处理的地方是 \(s\) 与 \(t\) 的交界处。 \(s\) 的最后一个字符的儿子要进行更改,过后也要记得还原。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn =(1 << 20) + 5;
int qian[maxn];
int ch[maxn][26], col[maxn], tot;
int fail[maxn];
void add(string s)
{
	int n =  s.size(), u = 0;
	for(int i = 1;i < n;i ++)
	{
		int p = s[i] - 'a';
		if(!ch[u][p]) ch[u][p] = ++ tot;
		u = ch[u][p];
	}
	col[u] ++;
}
void build()
{
	queue<int> q;
	for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i = 0;i < 26;i ++)
		{
			if(!ch[u][i]) ch[u][i] = ch[fail[u]][i];
			else fail[ch[u][i]] = ch[fail[u]][i],q.push(ch[u][i]);
		}
	}
}
int yuan[27];
int main(){
	freopen("text.in", "r", stdin);
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	string s;
	cin >> s;
	int n = s.size();
	s = ' ' + s;
	add(s);
	build();
	int Q;
	cin >> Q;
	while(Q --)
	{
		string t;
		cin >> t;
		int m = t.size();
		t = ' ' + t;
		int j = ch[n - 1][s[n] - 'a'];
		int p = tot;
		for(int i = 0;i < 26;i ++) yuan[i] = ch[tot][i];
		for(int i = 1;i <= m;i ++)
		{
			j = ch[j][t[i] - 'a'];
			ch[tot][t[i] - 'a'] = tot + 1;
			fail[tot + 1] =  ch[fail[tot]][t[i] - 'a'];
			cout << j << ' ';
			tot ++;
			for(int i = 0;i < 26;i ++) ch[tot][i] = ch[fail[tot]][i];
		}
		for(int i = 0;i < 26;i ++) ch[p][i] = yuan[i];
		for(int i = p + 1;i <= tot + 1;i ++)
		{
			for(int j = 0;j < 26;j ++) ch[i][j] = 0;
			if(i != p) fail[i] = 0;
		}
		tot = p;
		cout << endl;
	}
	return 0;
}

标签:字符,CF1721E,ch,int,题解,AC,KMP,自动机
From: https://www.cnblogs.com/closureshop/p/16862553.html

相关文章

  • 【题解】洛谷P2725 [USACO3.1]邮票 Stamps
    从n种邮票中选出不超过k张邮票,使选出来的邮票可以表示1~m之间(含)的所有数。每张邮票在不超过k的前提下,都可以使用无数次,因此可以将问题看成一个完全背包问题。n种邮票就是n......
  • [ARC069F]Flags 题解
    因为一个小错误整整调了一天qwq除去2-SAT部分没学过去学了一下,其它部分都想出来了,四舍五入我自己写了一道Cu,好欸(虽然这Cu好像非常水QAQ)。F-Flags一条数轴上有......
  • P8813(CSP-J2022T1)题解
    题意:算a ^ b,如果结果超出1e9就输出-1,反之输出结果。思路:边算边判加特判。代码:#include<cstdio>#definelllonglong#definemx1e9//边界usingnamespacestd;i......
  • P8814(CSP-J2022T2)题解
    题意:给定一个正整数 k,有k 次询问,每次给定三个正整数 n, e, d,求两个正整数 p, q,使 n = p × q且e × d =(p −1)×(q −1)+1。思路:通过题意可以发......
  • AtCoder Beginner Contest 276 A~G 题解
    今天凌晨CFD题差一句判断AC,晚上ABCG题把插板法和快速幂搞混差点AC。事不过三,再这样一次我直接紫砂。太简单的题就不放翻译和代码了。(事实上这场A-E都是大水题......
  • 个人比赛实况和题解
    CodeforcesCF1740:实况:https://pan.baidu.com/s/1BYjUp2Atvqkpqe3jVogPTQ(提取码:1104);题解:https://www.cnblogs.com/lucius7/p/16861747.html。AtCoderabc275:实况:https://p......
  • 洛谷P8757 [蓝桥杯 2021 省 A2] 完美序列 题解
    思路为使描述方便,先令题目描述中的“完美序列”反转(即序列单调递增且每一个数都是上一个数的倍数)。原“完美序列”与反转后的本质相同。先考虑最大长度。显然,当完美序列......
  • 洛谷P8775 [蓝桥杯 2022 省 A] 青蛙过河 题解 贪心+二分答案
    题目链接​​https://www.luogu.com.cn/problem/P8775​​题目大意小青蛙住在一条河边,它想到河对岸的学校去学习。小青蛙打算经过河里的石头跳到对岸。河里的石头排成了一条......
  • 洛谷-P8814 题解
    前言蒟蒻感叹!许多大佬的思路真的很复杂,于是为了部分没学过的人写了这篇题解(其实就是为了咕值),值得一看!正文♦算法:式子推导从题中可得\(\begin{cases}n_i=p_i\times......
  • 「题解报告」[POI2008]PER-Permutation
    「题解报告」[POI2008]PER-Permutation点击查看目录目录「题解报告」[POI2008]PER-Permutation思路代码不理解哪里难了,学过扩卢并且推一下式子基本就是两眼切吧。......