写一种考场上想到的线性做法。
根据性质,若 s[i,j−1]s[i,j−1] 与 s[j,k]s[j,k] 均为合法子串,那么 s[i,k]s[i,k] 必为合法子串。
考虑 dp,设 fifi 表示以第 ii 位作为结尾的合法子串数量,容易得出转移方程:fi=fgi−1+1fi=fgi−1+1,其中 gigi 表示最大且能使 s[gi,i]s[gi,i] 成为合法子串的下标。答案即为 ∑i=1nfi∑i=1nfi。
由于 gigi 是满足条件中最大的,因此必有 si=sgisi=sgi,我们就可以按下图的方式从 ii 往前跳,初始时 gi=i−1gi=i−1,之后不断令 gi→ggi−1gi→ggi−1,直到满足 si=sgisi=sgi。
但这样会被形如 abbccddeea
之类的数据退化到 n2n2 级别,因此考虑如何优化。
如果令 hi=gi−1hi=gi−1,会发现跳的方式变成了这样,形成了若干条链,由此在每个位置记录 ai,cai,c,表示 sai,c+1=csai,c+1=c 且能使 [ai,c+1,i][ai,c+1,i] 成为合法子串的最大的下标。
每次修改的值只有 ai,siai,si,我们便可以用 toitoi 表示该链的链头,每次修改 atoi,siatoi,si 即可。
时间复杂度:O(n)O(n)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+5;
int n,dp[N],a[N][26],to[N];
char s[N];
ll ans;
int main()
{
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)
{
to[i]=i;
int x=a[to[i-1]][s[i]-'a'];
if(x) to[i]=to[x-1],dp[i]=dp[x-1]+1;
a[to[i]][s[i]-'a']=i,ans+=dp[i];
}
printf("%lld\n",ans);
return 0;
}