题目大意
YJC最近写了一篇关于子串的论文。CJY看他那么喜欢子串,决定出一道题考考他。
CJY给出了一个字符串s,令s[l~r]表示s中区间[l,r]构成的子串,下标从1开始。他向YJC提出了许多询问,每次询问给了四个数i,j,k,l,求LCP(s[i~k],s[j~l]),即每次询问两个子串的最长公共前缀。YJC很轻松地解决了这个问题。CJY表示很不爽,于是他改变了一下问题,让YJC求所有合法的询问的答案的和,即设n=|S|,求
。YJC发现现在自己不会做了,于是他来向你求助。因为这个数可能很大,你只需要告诉他答案mod 998244353的值即可。
解题思路
先做一遍sa,对height前后做一次单调栈,分别是递增和不递减,求出点的数量和sa和,推出公式后可以直接得解,先把含
∑
的式子求出来,用等差数列求和公式前缀平方和公式求和即可。注意把答案乘二后加上自己乘自己的情况。
code
using namespace std;
LL const mn=5*1e5+9,mo=998244353;
LL n,m,a[2][mn],sa[mn],cnt[mn],h[mn],st[mn],f[mn],g[mn],suml[mn],sizel[mn];
LL *rank=a[0],*ord=a[1];
char s[mn];
void sort(){
fo(i,1,m)cnt[i]=0;
fo(i,1,n)cnt[rank[i]]++;
fo(i,1,n)cnt[i]+=cnt[i-1];
fd(i,n,1)sa[cnt[rank[ord[i]]]--]=ord[i];
}
LL diff(LL x,LL y,LL z){
return (ord[x]!=ord[y])||(ord[x+z]!=ord[y+z]);
}
int main(){
//freopen("substring.in","r",stdin);
//freopen("substring.out","w",stdout);
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
scanf("%s",s+1);n=strlen(s+1);
fo(i,1,n)rank[i]=s[i]-'a'+1,ord[i]=i;m=26;
sort();
for(LL w=1,p=0;p!=n;w<<=1){
p=0;fo(i,n-w+1,n)ord[++p]=i;
fo(i,1,n)if(sa[i]>w)ord[++p]=sa[i]-w;
sort();swap(rank,ord);rank[sa[1]]=p=1;
fo(i,2,n)rank[sa[i]]=p+=diff(sa[i],sa[i-1],w);
m=p;
}
for(LL i=1,j,p=0;i<=n;h[rank[i]]=p,i++)
for(j=sa[rank[i]-1],p=(p)?p-1:p;s[i+p]==s[j+p];p++);
LL ans=0;
fo(i,2,n){
LL tmp=0,tmp2=0;
while(st[0]&&(st[st[0]]>h[i]))tmp+=f[st[0]],tmp2+=g[st[0]],st[0]--;
st[++st[0]]=h[i];
suml[i]=f[st[0]]=(tmp+sa[i-1])%mo;
sizel[i]=g[st[0]]=tmp2+1;
}
st[0]=0;
fd(i,n,2){
LL tmp=0,tmp2=0;
while(st[0]&&(st[st[0]]>=h[i]))tmp+=f[st[0]],tmp2+=g[st[0]],st[0]--;
st[++st[0]]=h[i];
f[st[0]]=(tmp+sa[i])%mo;
g[st[0]]=tmp2+1;
LL l=h[i],sumr=f[st[0]],sizer=g[st[0]];
ans=(ans+((l*(l+1)/2-l*(l+1)*(2*l+1)/6)/2+l*(n+1-l)*(n+1-l)-l*l*(l+1)/2)%mo*(sizel[i]*sizer%mo)
-(l*(n+1-l)%mo)*((suml[i]*sizer+sumr*sizel[i])%mo)+(l*(l+1)/2%mo)*((((n+1)*sizel[i]-suml[i])*sizer+((n+1)*sizer-sumr)*sizel[i])%mo)
+l*suml[i]%mo*sumr)%mo;
}
ans<<=1;
fo(i,1,n)ans=(ans+(i*(i+1)/2-i*(i+1)*(2*i+1)/6)/2+i*i*(i+1)/2)%mo;
printf("%lld",ans);
return 0;
}