零、工具
SAM Drawer:一个画后缀自动机的工具
一、 模板
1. 后缀自动机(单串)
(1)插入(建SAM)
char s[N];
int ch[N*2][26],fa[N*2],len[N*2];
int last=1,tot=1;
void insert(int x)
{
int p=last;
int np=last=++tot;
len[np]=len[p]+1;
while(p&&!ch[p][x]) { ch[p][x]=np; p=fa[p]; }
if(!p) fa[np]=1;
else
{
int q=ch[p][x];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
for(int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[nq]=fa[q]; fa[np]=fa[q]=nq;
len[nq]=len[p]+1;
while(p&&ch[p][x]==q)
{ ch[p][x]=nq; p=fa[p]; }
}
}
}
int main()
{
scanf("%s",s+1);
int ls=strlen(s+1);
for(int i=1;i<=ls;i++)
insert(s[i]-'a');
return 0;
}
(2)匹配
int p=1,now=0;
for(int i=1;i<=lt;i++)
{
int x=t[i]-'a';
if(ch[p][x])
{ now++; p=ch[p][x];}
else
{
while(p&&!ch[p][x]) p=fa[p];
if(!p){p=1;now=0;}
else
{ now=len[p]+1; p=ch[p][x];}
}
}
2. 广义后缀自动机(多串)
广义自动机有在线/离线两种写法。(在线写法是流传比较广的,能通过大部分题目,但是有人说是假的,不知道为什么……)
(模板为洛谷广义后缀自动机)
(1)在线版
每次新加入一个串的时候,将last改为1。如果对应节点存在,进行修改;否则,新建节点。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N=2e6+10;
int ch[N][26],len[N],fa[N];
char s[N];
int last,tot=1;
int insert(int c,int last)
{
int p=last;
if(ch[p][c])
{
int np=ch[p][c];
if(len[p]+1==len[np]) return np;
else
{
int nq=++tot;
len[nq]=len[p]+1;
for(int i=0;i<26;i++) ch[nq][i]=ch[np][i];
while(p&&ch[p][c]==np) ch[p][c]=nq,p=fa[p];
fa[nq]=fa[np],fa[np]=nq;
return nq;
}
}
int q=++tot;
len[q]=len[p]+1;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int np=ch[p][c];
if(len[p]+1==len[np]) fa[q]=np;
else
{
int nq=++tot;
len[nq]=len[p]+1;
for(int i=0;i<26;i++) ch[nq][i]=ch[np][i];
while(p&&ch[p][c]==np) ch[p][c]=nq,p=fa[p];
fa[nq]=fa[np],fa[np]=fa[q]=nq;
}
}
return q;
}
int main()
{
int n;
LL ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
last=1;
int l=strlen(s+1);
for(int j=1;j<=l;j++) last=insert(s[j]-'a',last);
}
for(int i=2;i<=tot;i++) ans+=len[i]-len[fa[i]];
printf("%lld ",ans);
return 0;
}
(2)离线版
先将所有串放在一起建立一棵Trie树,然后按照bfs序建立后缀自动机。(当有两棵树的时候,封进结构体太爽了)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
using namespace std;
const int N=2e6+10;
const int M=1e6+10;
char ss[M];
struct Trie{
int tot,col[M],fa[M],ch[M][26];
Trie(){tot=1;}
void insert(char s[])
{
int now=1;
for(int i=1;s[i];i++)
{
int c=s[i]-'a';
if(!ch[now][c])
ch[now][c]=++tot,fa[tot]=now,col[tot]=c;
now=ch[now][c];
}
}
}T1;
struct Suffix_Auto{
int tot,pos[N],fa[N],len[N],ch[N][26];
queue<int>Q;
Suffix_Auto(){tot=1;}
int insert(int c,int last)
{
int p=last;
int q=++tot;
len[q]=len[p]+1;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p)fa[q]=1;
else
{
int np=ch[p][c];
if(len[p]+1==len[np])fa[q]=np;
else
{
int nq=++tot;
len[nq]=len[p]+1;
for(int i=0;i<26;i++)
ch[nq][i]=ch[np][i];
while(p&&ch[p][c]==np)
ch[p][c]=nq,p=fa[p];
fa[nq]=fa[np],fa[q]=fa[np]=nq;
}
}
return q;
}
void build()
{
for(int i=0;i<26;i++)
if(T1.ch[1][i])
Q.push(T1.ch[1][i]);
pos[1]=1;
while(Q.empty()==0)
{
int x=Q.front();
Q.pop();
pos[x]=insert(T1.col[x],pos[T1.fa[x]]);
for(int i=0;i<26;i++)
if(T1.ch[x][i])
Q.push(T1.ch[x][i]);
}
}
void query()
{
LL ans=0;
for(int i=2;i<=tot;i++) ans+=len[i]-len[fa[i]];
printf("%lld",ans);
}
}SAM;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",ss+1);
T1.insert(ss);
}
SAM.build();
SAM.query();
return 0;
}
二、题目
1. CF gym100956 K
后缀自动机模板题
题意:给出S,T两个字符串。设S的一个子串为s[l,……r],要求s在T中出现,且r-l+1-max(l-1,ls-r)最大,其中ls为S串的长度。
题解:当l确定的时候,r越大,结果不会更差,所以,对于每一个l,求其最大匹配位置进行更新答案即可。用T串倒序建后缀自动机,用S在后缀自动机上跑匹配。
const int N=1e6+10;
const int inf=1e9+10;
char s[N],t[N];
int ch[N*2][26],fa[N*2],len[N*2],R[N],ans[N];
int last=1,tot=1;
void insert(int x)
{
int p=last;
int np=last=++tot;
len[np]=len[p]+1;
while(p&&!ch[p][x]) { ch[p][x]=np; p=fa[p]; }
if(!p) fa[np]=1;
else
{
int q=ch[p][x];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
for(int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[nq]=fa[q]; fa[np]=fa[q]=nq;
len[nq]=len[p]+1;
while(p&&ch[p][x]==q)
{ ch[p][x]=nq; p=fa[p]; }
}
}
}
int main()
{
int maxans=-inf,pos=-1;
scanf("%s",s+1);
int ls=strlen(s+1);
scanf("%s",t+1);
int lt=strlen(t+1);
for(int i=lt;i>0;i--) insert(t[i]-'a');
int p=1,now=0;
for(int i=1;i<=ls;i++) ans[i]=-inf;
for(int i=ls;i>0;i--)
{
int x=s[i]-'a';
if(ch[p][x]){ p=ch[p][x]; now++;}
else
{
while(p&&!ch[p][x]) p=fa[p];
if(p) { now=len[p]+1; p=ch[p][x];}
else { now=0; p=1; }
}
if(now)
{
ans[i]=now-max(i-1,ls-(i+now-1));
R[i]=i+now-1;
}
}
for(int i=1;i<=ls;i++)
if(ans[i]>maxans) { maxans=ans[i]; pos=i; }
if(pos==-1) { printf("-1 -1"); return 0; }
int i;
for(i=pos;i<=R[pos];i++)
if(i-pos+1-max(pos-1,ls-i)==ans[pos])
break;
printf("%d %d",pos-1,i-1);
return 0;
}
2. (SPOJ8222)
利用后缀树
统计每一长度的出现频率最高的子串的出现次数
题解:按拓扑序统计,统计时向父节点更新即可。
void cal()
{
for(int i=1;i<=cnt;i++) c[mx[i]]++;
for(int i=1;i<=n;i++) c[i]+=c[i-1];
for(int i=cnt;i>0;i--) a[c[mx[i]]--]=i;
for(int i=cnt;i>0;i--)
{
int p=a[i];
size[fa[p]]+=size[p];
ans[mx[p]]=max(ans[mx[p]],size[p]);
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
root=last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
cal();
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
3. (SPOJ) LCS(最长公共子串)
题意:求S与T的最长公共子串
题解:以S建树,用T在自动机上跑匹配。
假设T[1……i-1]的后缀在后缀自动机上最大匹配位置为p,maxl为T[1……i-1]的后缀的最大匹配长度:
(1) ch[p][T[i]-‘a’]存在,即可以继续向下匹配到T串上的第i位,则T[1……i]的最大匹配位置为ch[p][T[i]-‘a’],最大匹配位置为maxl+1;
(2)ch[p][T[i]-‘a’]不存在,则向p的父节点跳转:
(a)如果跳转到某一位置p’,ch[p’][T[i]-‘a’]存在,则最大匹配位置为p’,最大匹配长度为len[p’]+1;
(b)如果一直跳转到0也没有匹配的位置,则最大匹配长度为0,(记得将p移动到树根)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=250010;
char s[N],t[N];
int ch[N*2][26],len[N*2],fa[N*2];
int last=1,tot=1;
void insert(int x)
{
int p=last;
int np=last=++tot;
len[np]=len[p]+1;
while(p&&!ch[p][x]) { ch[p][x]=np; p=fa[p]; }
if(!p) fa[np]=1;
else
{
int q=ch[p][x];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
for(int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[nq]=fa[q]; fa[np]=fa[q]=nq;
len[nq]=len[p]+1;
while(p&&ch[p][x]==q)
{ ch[p][x]=nq; p=fa[p]; }
}
}
}
int main()
{
int ans=0,now=0;
scanf("%s",s+1); scanf("%s",t+1);
int ls=strlen(s+1); int lt=strlen(t+1);
for(int i=1;i<=ls;i++)
insert(s[i]-'a');
int p=1;
for(int i=1;i<=lt;i++)
{
int x=t[i]-'a';
if(ch[p][x])
{ p=ch[p][x]; now++;}
else
{
while(p&&!ch[p][x]) p=fa[p];
if(p)
{ now=len[p]+1; p=ch[p][x];}
else
{ now=0; p=1; }
}
ans=max(ans,now);
}
printf("%d",ans);
return 0;
}
4. (SPOJ) LCS2
题意:10个字符串求最长公共子串
题解:将一个字符串建树,其他字符串在上面匹配。统计每个字符串在不同点上匹配的最大长度,并用其更新该点和该点的父节点。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int N=1e5+10;
char s[N];
int ch[N*2][26],fa[N*2],len[N*2];
int last=1,tot=1;
void insert(int x)
{
int p=last;
int np=last=++tot;
len[np]=len[p]+1;
while(p&&!ch[p][x]) { ch[p][x]=np; p=fa[p]; }
if(!p) fa[np]=1;
else
{
int q=ch[p][x];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
for(int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[nq]=fa[q]; fa[np]=fa[q]=nq;
len[nq]=len[p]+1;
while(p&&ch[p][x]==q)
{ ch[p][x]=nq; p=fa[p]; }
}
}
}
void get_tuopu()
{
for(int i=1;i<=tot;i++)
c[len[i]]++;
for(int i=1;i<=tot;i++)
c[i]+=c[i-1];
for(int i=1;i<=tot;i++)
rk[c[len[i]]--]=i;
}
int main()
{
scanf("%s",s+1);
int ls=strlen(s+1);
for(int i=1;i<=ls;i++)
insert(s[i]-'a');
get_tuopu();
for(int i=1;i<=tot;i++)
mn[i]=len[i];
while(scanf("%s",t+1)!=EOF)
{
int lt=strlen(t+1);
int p=1,now=0;
for(int i=1;i<=tot;i++)
h[i]=0;
for(int i=1;i<=lt;i++)
{
int x=t[i]-'a';
if(ch[p][x])
{
now++; p=ch[p][x];
h[p]=max(h[p],now);
}
else
{
while(p&&!ch[p][x]) p=fa[p];
if(!p)
{ p=1; now=0; }
else
{
now=len[p]+1; p=ch[p][x];
h[p]=max(h[p],now);
}
}
}
for(int i=tot;i>0;i--)
{
int x=rk[i];
mn[x]=min(mn[x],h[x]);
h[fa[x]]=max(h[fa[x]],min(h[x],len[fa[x]]));
}
}
int ans=0;
for(int i=1;i<=tot;i++)
ans=max(ans,mn[i]);
printf("%d",ans);
return 0;
}
5.牛客多校2020第2场A
广义后缀自动机+KMP
6.[SCOI2012]喵星球上的点名
题意:每一个人的名字由两个字符串构成,如果询问串为某个人的某个串的子串,那么这个人即被点到。对于每一个询问串,输出点到了几个人。全部询问结束后,输出每个人被点到的次数。
思路:
对名字建广义后缀自动机。
询问一:预处理每一个节点会被多少个人的名字包含。枚举每一个人的名字,对于每一个节点,向上跳fa,对其所有后缀的sz进行更新。对每一个询问,找到对应节点,输出sz即可。
询问二:对于每一个询问串,对其所在节点vist更新。全部询问结束后,枚举每一个人的名字,对于其中每一个节点,向上统计其所有后缀的出现次数。
由于字符集较大,需要用map记录
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<vector>
#define LL long long
using namespace std;
const int N=4e5+10;
map<int, int> ch[N];
int len[N],fa[N], lazy[N], ans[N], vist[N], sz[N], ss[N], a[N][2];
vector<int>s[N][2];
int tot=1;
int insert(int c,int last)
{
int p=last;
if(ch[p].find(c) != ch[p].end())
{
int np=ch[p][c];
if(len[p]+1==len[np]) return np;
else
{
int nq=++tot;
len[nq]=len[p]+1;
ch[nq]=ch[np];
while(p && ch[p].find(c) != ch[p].end() && ch[p][c]==np) ch[p][c]=nq,p=fa[p];
fa[nq]=fa[np],fa[np]=nq;
return nq;
}
}
int q=++tot;
len[q]=len[p]+1;
while(p&& ch[p].find(c) == ch[p].end()) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int np=ch[p][c];
if(len[p]+1==len[np]) fa[q]=np;
else
{
int nq=++tot;
len[nq]=len[p]+1;
ch[nq]=ch[np];
while(p && ch[p].find(c) != ch[p].end() && ch[p][c]==np) ch[p][c]=nq,p=fa[p];
fa[nq]=fa[np],fa[np]=fa[q]=nq;
}
}
return q;
}
void update_sz(int x, int y)
{
for(;x && lazy[x] != y; x = fa[x])
{
lazy[x] = y;
sz[x]++;
}
}
void update_ans(int x, int y)
{
for(; x && lazy[x] != y; x = fa[x])
{
lazy[x] = y;
ans[y] += vist[x];
}
}
int main()
{
int n, m;
scanf("%d%d",&n, &m);
for(int i=1;i<=n;i++)
{
for(int k = 0; k <= 1; k++)
{
int last = 1;
scanf("%d", &a[i][k]);
for(int j = 1; j <= a[i][k]; j++)
{
int c;
scanf("%d", &c);
s[i][k].push_back(c);
last=insert(c, last);
}
}
}
for(int i = 1; i <= n; i++)
{
for(int k = 0; k <= 1; k++)
{
int now = 1;
for(int j = 0; j < a[i][k]; j++)
{
now = ch[now][s[i][k][j]];
update_sz(now, i);
}
}
}
for(int i = 1;i <= m; i++)
{
int k;
scanf("%d", &k);
int now = 1;
int flag = 1;
for(int j = 1;j <= k; j++)
scanf("%d", &ss[j]);
for(int j = 1; j <= k; j++)
{
int c = ss[j];
if(ch[now].find(c) == ch[now].end())
{
flag = 0;
break;
}
else
now = ch[now][c];
}
if(flag)
{
printf("%d\n", sz[now]);
vist[now]++;
}
else
printf("0\n");
}
for(int i = 1; i <= tot; i++) lazy[i] = 0;
for(int i = 1; i <= n; i++)
{
for(int k = 0; k <= 1; k++)
{
int now = 1;
for(int j = 0; j < a[i][k]; j++)
{
now = ch[now][s[i][k][j]];
update_ans(now, i);
}
}
}
for(int i = 1;i <= n; i++)
{
printf("%d ",ans[i]);
}
printf("\n");
return 0;
}