字符串专题复习

字符串已经忘光了,只好花了一天时间来复习


KMP

一篇好的讲解

KMP模板

code:

#include<bits/stdc++.h>
using namespace std;
string s,t;
int n,m;
int nxt[1000006];
void getnxt(){
	nxt[0]=-1;int i=0,j=-1;
	while(i<m){
		if(j==-1||t[i]==t[j])
			i++,j++,nxt[i]=j;
		else j=nxt[j];
	}
}
void match(){
	int j=0;
	for(int i=0;i<n;i++){
		while(s[i]!=t[j]&&j!=-1)j=nxt[j];
		j++;if(j==m)cout<<i-m+2<<'\n';
	}
}
signed main(){
	cin>>s>>t;n=s.length(),m=t.length();
	getnxt();
	match();
	for(int i=1;i<=m;i++)
		cout<<nxt[i]<<" ";	
	return 0;
}

AC自动机

trie树上的KMP

将多个模式串上trie树,一个文本串来匹配模式串。

fail 类似于 KMP 的 nxt

概括 AC自动机 求 fail 的过程:

1.对整个字典树进行宽度优先遍历。
2.若当前搜索到点 \(x\),那么对于 \(x\) 的第 \(i\) 个儿子(也就是代表字符 \(i\) 的儿子),一直往 \(x\) 的 fail 跳,直到跳到某个点也有 \(i\) 这个儿子,\(x\) 的第 \(i\) 个儿子的 fail 就指向这个点的儿子 \(i\)

形式上,AC 自动机基于由若干模式串构成的 Trie 树,并在此之上增加了一些 fail 边;本质上,AC 自动机是一个关于若干模式串的 DFA(确定有限状态自动机),接受且仅接受以某一个模式串作为后缀的字符串。

并且,与一般自动机不同的,AC 自动机还有 关于某个模式串的接受状态,也就是与某个模式串匹配(以某个模式串为后缀)的那些状态,即某个模式串在 Trie 树上的终止节点在 fail 树上的整个子树。

AC自动机模板

关于 last 其实是个路径压缩,保证跳的时候一定跳到有模式串的地方,可以把 暴力跳fail 优化到 \(O(模式串总长)\)

code:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
char s[N*10];
int c[N][26],tot,b[N],fail[N],last[N],n;
int ans[N];
int flag[N];
void insert(int T){
	cin>>s;
	int u=0,len=strlen(s);
	for(int i=0;i<len;i++){
		int t=s[i]-'a';
		if(!c[u][t]) c[u][t]=++tot;
		u=c[u][t];
	}
	if(b[u])flag[T]=b[u];
	else b[u]=T;
}
void getfail(){
	queue<int> q;
	for(int i=0;i<26;i++) if(c[0][i]) q.push(c[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++){
			int v=c[u][i];
			if(!v){c[u][i]=c[fail[u]][i];continue;}
			q.push(v);fail[v]=c[fail[u]][i];
			last[v]=b[fail[v]]?fail[v]:last[fail[v]];
		}
	}
}
void find(){
	cin>>s;
	int u=0,len=strlen(s);
	for(int i=0;i<len;i++){
		int t=s[i]-'a',temp=0;
		u=c[u][t];
		if(b[u])temp=u;
		else if(last[u])temp=last[u];
		while(temp){
			ans[b[temp]]++;
			temp=last[temp];
		}
	}
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++)insert(i);
	getfail();find();	
	for(int i=1;i<=n;i++)cout<<(flag[i]?ans[flag[i]]:ans[i])<<'\n'; 
}
int main(){
	solve();
	return 0;
}

manacher

入门时的博客

注意其中对于 \(hw_i\) 初始化部分,hw[i]=min(hw[(mid<<1)-i],hw[mid]+mid-i);

其中 hw[mid]+mid-i 等价于 maxright-i​ 这实际上是 \(i\)\(mid\) 左边相对应的 \(j\) 所受的限制,因为 \(hw_j\) 可能会超出 \(mid-hw[mid]\) 的范围,所以需要限定最大值。画图之后翻折过去就能理解。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=11000005;
string t="@#";int n;
inline void gett(){
	char c=getchar();
	while(c>'z'||c<'a')c=getchar();
	while(c>='a'&&c<='z')t+=c,t+="#",c=getchar();
	n=t.length();
}
int hw[2*N],mr,mid,ans;
inline void match(){
	for(int i=1;i<n;i++){
		hw[i]=(mr>i)?min(hw[(mid<<1)-i],mr-i):1;
		while(t[i+hw[i]]==t[i-hw[i]])hw[i]++;
		if(i+hw[i]>mr)mr=i+hw[i],mid=i;
		ans=max(ans,hw[i]);
	}
}
int main(){
	gett();
	match();
	cout<<ans-1;
	return 0;
}

update on 2022.1.21


Z算法

here


Z-algorithm & kmp & 循环节

here


后缀数组

here


posted @ 2021-10-08 18:19  llmmkk  阅读(42)  评论(0编辑  收藏  举报