上一篇我们具体介绍了后缀自动机的构造方式,但并没有谈它的应用,那么我们在这一篇里详细谈一谈后缀自动机的应用
首先,后缀自动机的性质:从根节点开始可以识别一个字符串的所有子串
接下来,我们需要给出一个定义:
将后缀自动机上的pre指针反指,会得到一棵树形结构,我们把这棵树叫做parent树!
parent树是原串反串的后缀树!
记住这个定义,因为后缀自动机中大部分题都与这棵树有千丝万缕的联系。
我们画出前文后缀自动机的parent树(即串abbab)
(注意:这里边上画出的a,b主要用于区分这两个节点,暂不讨论实际意义)
用绿色标记的是分裂节点。
那么可以发现,在parent树上父节点一定是子节点的一个后缀
那么我们讨论一个经典问题:
一个询问字符串在原模板串中出现了多少次呢?
这个问题有很多种解法,包括hash,kmp等等,时间复杂度比较优越。
这里介绍一种后缀自动机的方法,时间复杂度O(卡死)
我们从parent树出发
我们知道,一个字符串的所有子串是它前缀的后缀
而我们是可以在建造后缀自动机的时候标记前缀节点的!(事实上,除了分裂出来的节点,其他节点都属于前缀节点,因为都能在事实上对应出原串的一个前缀)
同时,如果询问串是模板串的一个子串,那么这个询问串一定是这个模板串一个前缀的后缀!
所以我们先在后缀自动机上识别询问串,如果无法识别直接答案为0
如果成功识别,那么我们只需要求出这个串是多少个前缀的后缀就可以了
我们知道,parent树上的一个父节点是他所有子节点的后缀
所以,我们只需要统计出以这个节点为根的子树内前缀节点的个数就可以了!
而前缀节点只需在后缀自动机上打endpos标记就可以了!
这样这个问题就圆满地解决了
虽然实际上,这个问题没有任何应用后缀自动机的价值,而且用后缀自动机反而会导致时空超限,但我们还是以他为起点,因为后面的大部分应用都要基于这种方法
接下来看看模板题:
例:洛谷3804https://www.luogu.org/problemnew/show/P3804
求一个字符串中子串长度与出现次数乘积最大值,要求出现次数不为1
有了上面的经验,我们已经知道如何统计一个子串出现次数了
而一个子串的长度,不就是len变量吗?
那就完事了呀!在parent树上跑一遍dfs即可!
代码如下:
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
struct SAM
{
int tranc[27];
int len;
int pre;
int v;
int endpos;
}s[5000005];
struct Edge
{
int next;
int to;
}edge[2000005];
int head[5000005];
char ss[1000005];
int cnt=1;
ll ans=0;
int siz;
int las;
void init()
{
memset(head,-1,sizeof(head));
cnt=1;
}
void add(int l,int r)
{
edge[cnt].next=head[l];
edge[cnt].to=r;
head[l]=cnt++;
}
/*void res()
{
las=++siz;
}*/
void ins(int c)
{
int nwp=++siz;
s[nwp].len=s[las].len+1;
s[nwp].endpos=1;
int lsp;
for(lsp=las;lsp&&!s[lsp].tranc[c];lsp=s[lsp].pre)s[lsp].tranc[c]=nwp;
if(!lsp)
{
s[nwp].pre=1;
}else
{
int lsq=s[lsp].tranc[c];
if(s[lsq].len==s[lsp].len+1)
{
s[nwp].pre=lsq;
}else
{
int nwq=++siz;
s[nwq]=s[lsq];
s[nwq].endpos=0;
s[nwq].len=s[lsp].len+1;
s[lsq].pre=s[nwp].pre=nwq;
while(s[lsp].tranc[c]==lsq)
{
s[lsp].tranc[c]=nwq;
lsp=s[lsp].pre;
}
}
}
las=nwp;
}
void buildtree()
{
for(int i=2;i<=siz;i++)
{
add(s[i].pre,i);
}
}
void dfs(int x)
{
for(int i=head[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
dfs(to);
s[x].endpos+=s[to].endpos;
}
if(s[x].endpos>1)ans=max(ans,1ll*s[x].len*s[x].endpos);
}
int main()
{
scanf("%s",ss+1);
init();
int lent=strlen(ss+1);
las=++siz;
for(int i=1;i<=lent;i++)
{
ins(ss[i]-'a'+1);
}
buildtree();
dfs(1);
printf("%lld\n",ans);
return 0;
}