前言
史上最通俗的后缀自动机详解:https://www.luogu.com.cn/blog/Kesdiael3/hou-zhui-zi-dong-ji-yang-xie
题意
设小写字母字符串(s),长度为(n),(s[l:r])表示第(l)个到第(r)个字符构成的子串。
定义半前缀是(s[1:i]+s[j:k]),其中(0≤i<len(s),i<j≤len(s),j−1≤k≤len(s))。直观上来说,你可以把半前缀理解成某一个前缀(s[1:k])删除掉某一个子串后形成的结果(当然也允许不删)。
给出字符串(s),你需要求出(s)的所有半前缀中,有多少个不同的字符串。
思路
对于一个半前缀(s[1:i]+s[j:k]),如果有(s[1:i+1]+s[j+1:k])与其相等即(s[i+1]=s[j])时,则说明这个半前缀重复了。因此,对于每个前缀(s[1:i]),我们统计(s[i+1:n])中有多少不同子串不以(s[i+1])开始,把这些加起来就是答案。我们将串倒序加入后缀自动机,这样我们就能够知道每次以(s[i])开始的子串数量。然后对于每个前缀统计答案即可。注意计算空串。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxx = 1e6+10;
struct node
{
int ch[26];
int len,fa;
node(){memset(ch,0,sizeof(ch));len=0;}
}dian[maxx];
int las=1,tot=1;
void add(int c)
{
int p=las;int np=las=++tot;
dian[np].len=dian[p].len+1;
for(;p&&!dian[p].ch[c];p=dian[p].fa)dian[p].ch[c]=np;
if(!p)dian[np].fa=1;
else
{
int q=dian[p].ch[c];
if(dian[q].len==dian[p].len+1)dian[np].fa=q;
else
{
int nq=++tot;dian[nq]=dian[q];
dian[nq].len=dian[p].len+1;
dian[q].fa=dian[np].fa=nq;
for(;p&&dian[p].ch[c]==q;p=dian[p].fa)dian[p].ch[c]=nq;
}
}
}
char s[maxx];
LL cnt[26];
int main()
{
scanf("%s",s+1);
int n=strlen(s+1);
LL sum=1,ans=1;
for(int i=n;i>=1;i--)
{
add(s[i]-'a');
LL tmp=dian[las].len-dian[dian[las].fa].len;
cnt[s[i]-'a']+=tmp;
sum+=tmp;
ans+=sum-cnt[s[i]-'a'];
}
printf("%lld
",ans);
return 0;
}