字符串(不)全家桶
哈希
略
KMP
求next数组
ne[1]=0;
for(int i=2,j=0;i<=n;i++){//i表示当前搜索位置,不回溯,j扫描前缀
while(j&&p[i]!=p[j+1]) j=ne[j];
if(p[i]==p[j+1]) j++;
ne[i]=j;
}
AC自动机
const int maxn=3e4+10;
int n,tr[maxn][2],fail[maxn],tot,e[maxn],rd[maxn],cnt[maxn];
void build(string s){
int u=0;
for(int i=0;i<s.size();i++){
if(!tr[u][s[i]-'0']) tr[u][s[i]-'0']=++tot;
u=tr[u][s[i]-'0'];
}
e[u]++;
}
queue<int>q;
void bf(){
for(int i=0;i<=1;i++)
if(tr[0][i]) q.push(tr[0][i]);
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<1;i++){
if(tr[u][i]){
fail[tr[u][i]]=tr[fail[u]][i];
rd[tr[u][i]]++;
q.push(tr[u][i]);
}
else tr[u][i]=tr[fail[u]][i];
}
}
}
void qry(string s){
int u=0;
for(int i=0;i<s.size();i++){
u=tr[u][s[i]-'0'];
cnt[u]++;
}
}
void topu(){
queue<int>q;
for(int i=0;i<=1;i++)
if(!rd[tr[0][i]]) q.push(tr[0][i]);
while(q.size()){
int u=q.front();
q.pop();
int v=fail[u];
cnt[v]+=cnt[u];
rd[v]--;
if(!rd[v]) q.push(v);
}
}
扩展KMP
//类比普通kmp算法的next和kmp数组求法
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e7+10;
string a,b;
long long ansa,ansb;
int nxt[maxn],ext[maxn],lena,lenb;//nxt[]==z[]
int main(){
std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>a;
cin>>b;
lena=a.size();
lenb=b.size();
int p0=1,now=0;
nxt[0]=lenb;
while(now+1<lenb&&b[now]==b[now+1]) ++now;
nxt[1]=now;
for(int i=2;i<lenb;i++){
if(i+nxt[i-p0]<p0+nxt[p0]) nxt[i]=nxt[i-p0];
else{
now=p0+nxt[p0]-i;
now=max(now,0);
while(now+i<lenb&&b[now]==b[now+i]) ++now;
nxt[i]=now;
p0=i;
}
}
for(int i=1;i<=lenb;i++) ansb^=1ll*i*(nxt[i-1]+1);
now=0;
p0=0;
while(now<lenb&&now<lena&&a[now]==b[now]) ++now;
ext[0]=now;
for(int i=1;i<lena;i++){
if(i+nxt[i-p0]<p0+ext[p0]) ext[i]=nxt[i-p0];
else{
now=p0+ext[p0]-i;
now=max(now,0);
while(now<lenb&&i+now<lena&&b[now]==a[now+i]) ++now;
ext[i]=now;
p0=i;
}
}
for(int i=1;i<=lena;i++) ansa^=1ll*i*(ext[i-1]+1);
cout<<ansb<<'\n'<<ansa;
return 0;
}
Manacher
#include<bits/stdc++.h>
using namespace std;
const int maxn=1.1e7+10;
string s;
char a[maxn<<1];
int n,l[maxn<<1],ans;
void pre(){
a[1]='|';
n=s.size();
for(int i=1;i<=n;i++) a[i<<1]=s[i-1],a[(i<<1)|1]='|';
n=(n<<1)|1;
}
void dd(){
for(int i=1,c=0,r=-1;i<=n;i++){
l[i]=(i<=r?min(l[2*c-i],r-i):1);
while(i-l[i]>=1&&i+l[i]<=n&&a[i-l[i]]==a[i+l[i]]) ++l[i];
if(i+l[i]-1>r) c=i,r=i+l[i]-1;
ans=max(ans,l[i]-1);
}
}
int main(){
std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>s;
pre();
dd();
cout<<ans;
return 0;
}
后缀数组
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
string s;
int ord[128];
int sa[maxn],rnk[maxn],tmp[maxn],n,m,cnt[maxn];//sa是通过排名找串,rnk是以第i位为起点的后缀的排名,cnt是前缀和数组记录当前排名前的串的数量,tmp是过渡数组
void dd(){//倍增nlogn处理
for(int i=1;i<=n;i++) rnk[i]=ord[(int)s[i-1]],cnt[rnk[i]]++;//初始化该位置的排名以及该排名的数量
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];//求前缀和方便后面确定各位置字符的排名
for(int i=n;i>=1;i--) sa[cnt[rnk[i]]--]=i;//通过前缀和数组求出每个排名串的位置
for(int w=1,t=0;;m=t,t=0,w<<=1){//m是排名值域,每次改变排名值域
for(int i=n-w+1;i<=n;i++) tmp[++t]=i;//不足位数的靠前放
for(int i=1;i<=n;i++)
if(sa[i]>w) tmp[++t]=sa[i]-w;//分别算该块排名
for(int i=0;i<=m;i++) cnt[i]=0;
for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
swap(rnk,tmp);
t=0;
for(int i=1;i<=n;i++)
rnk[sa[i]]=(tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+w]==tmp[sa[i-1]+w])?t:++t;//根据上层数据排本层序,注意去重
if(t==n) break;//不可能有两个后缀相同,一定有且仅有n个不同排名,有n个排名就跳出循环
}
}
int main(){
std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>s;
n=s.size();
for(int i='0';i<='9';i++) ord[i]=++m;
for(int i='A';i<='Z';i++) ord[i]=++m;
for(int i='a';i<='z';i++) ord[i]=++m;
dd();
for(int i=1;i<=n;i++) cout<<sa[i]<<" ";
return 0;
}
求height数组(求与排名上一位的后缀的lcp长度,求两点lcp可转化为区间求min)
void geth(){
for(int i=1,k=0;i<=n;i++){
if(k) --k;
while(s[i+k-1]==s[sa[rnk[i]-1]+k-1]) ++k;
h[rnk[i]]=k;
}
}
后缀自动机SAM
注意非结构体内不能定义变量名为link
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
typedef long long ll;
string s;
ll ans;
int tot,to[maxn],nxt[maxn],h[maxn];
struct SAM{
int t[maxn][26],link[maxn],len[maxn],sum[maxn];//t是trie树,link是parent树的父亲,len是到该位置串长,sum记录串出现次数
int last,tot;//last是上一个加的节点
SAM(){
memset(t,0,sizeof(t));
memset(link,0,sizeof(link));
memset(len,0,sizeof(len));
memset(sum,0,sizeof(sum));
last=1;
tot=1;
}
inline void insert(int c){//新建节点
int p=last,np=last=++tot; //np是newpos
sum[np]=1;
len[np]=len[p]+1;//比其上一个添加的点长1
while(p&&!t[p][c]) t[p][c]=np,p=link[p];//其父节点没有该字符的儿子,向该字符连边
if(!p) link[np]=1;//第一次出现,连到根节点上
else{//以前出现过 进行分裂操作
int q=t[p][c];
if(len[q]==len[p]+1) link[np]=q;//刚好是后缀直接连在尾结点上
else{
int nq=++tot;//新节点
memcpy(t[nq],t[q],sizeof(t[q]));//信息与原节点相同
len[nq]=len[p]+1;//成为父节点的一个后缀
link[nq]=link[q];//连旧节点的父亲
link[np]=link[q]=nq;//连接分裂出的新节点和旧节点、新建节点
while(p&&t[p][c]==q) t[p][c]=nq,p=link[p];//连旧节点的边改连新分裂出的节点
}
}
}
}sam;
inline void adde(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
inline void dfs(int x){
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
dfs(y);
sam.sum[x]+=sam.sum[y];//计算每个串出现次数,儿子必定经过父亲
}
if(sam.sum[x]!=1) ans=max(ans,1ll*sam.sum[x]*sam.len[x]);
}
int main(){
cin>>s;
for(int i=0;i<(int)s.size();i++) sam.insert(s[i]-'a');
for(int i=2;i<=sam.tot;i++) adde(sam.link[i],i);//建parent树
dfs(1);
cout<<ans;
return 0;
}
回文自动机PAM
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
char s[maxn];
int n,lst,len[maxn],lans;
int now,tot=1,fail[maxn],cnt[maxn],t[maxn][26];
inline int getfail(int u,int p){
while(p-len[u]-1<=0||s[p-len[u]-1]!=s[p]) u=fail[u];//不匹配或匹配不了就往上跳
return u;
}
inline void insert(char c,int id){
int p=getfail(now,id);
if(!t[p][c-'a']){//没这种儿子才加,不然重复了
fail[++tot]=t[getfail(fail[p],id)][c-'a'];//先建失配指针
t[p][c-'a']=tot;
len[tot]=len[p]+2;//每增加一个字符长度比其父亲多2
cnt[tot]=cnt[fail[tot]]+1;//回文子串数比其父亲多1
}
now=t[p][c-'a'];
}
int main(){
// std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
fail[0]=1;//在长度为偶数的子树内匹配不了,朝着奇数树跳
len[1]=-1;//防止长度为奇数的串出问题
scanf("%s",(s+1));//由于8行,必须这样写
n=strlen(s+1);
for(int i=1;i<=n;i++){
char c;
if(i>1) s[i]=(s[i]-97+lans)%26+97;
insert(s[i],i);
lans=cnt[now];
cout<<lans<<'\n';
}
return 0;
}

浙公网安备 33010602011771号