从题目讲起
我们定义2个字符串的相似度等于两个串的相同前缀的长度。例如 “abc” 同 “abd” 的相似度为2,”aaa” 同 “aaab” 的相似度为3。
给出一个字符串S,计算S同他所有后缀的相似度之和。例如:S = “ababaa”,所有后缀为:
ababaa 6
babaa 0
abaa 3
baa 0
aa 1
a 1
S同所有后缀的相似度的和 = 6 + 0 + 3 + 0 + 1 + 1 = 11
想法
作为一个拿SAM模板随便刷水的人,这道题很明显反着插,求出每个状态right集合大小,从整个串的last一直沿着parent边往上跑,统计就行了。
结果爆了空间
没办法学扩展KMP
给定目标串S[1..m]和模板串T[1..n],求出目标串每个后缀与模板串的匹配长度。
设这个长度ext[i]。
假设现在1~i-1的ext都求出来了,现在要求i的,怎么做呢?
引入一个新数组next[i],表示T[i..n]与T的最大匹配长度。
设p=max(ext[a]+a-1),即之前最长匹配到S的哪里了。p取最大时,设位置为a。
整理一下:目前T[1..p-a+1]=S[a..p](①),那么T[i-a+1..p-a+1]=S[i..p](②).
我们要求S[i..m]和T[1..n]最长匹配,实质上,S[i..p]这一段我们是不用和T[1..p-i+1]匹配的了。由②得,问题可以转化为T[1..p-i+1]与T[i-a+1..p-a+1]的匹配,那么他们匹配长度ext就是min(next[i-a+1],p-i+1),为什么要取min?因为我们最多只到p,后面的在S中,我们还没匹配到过。
如果min取左边,就不用继续做了,因为后面一位肯定不相等,不然就不符合next数组性质。
取右边,就再往后匹配,并更新a,p。这样就能O(n)匹配,因为p是不断往后的。
匹配模块完成了,但是前提是要求next。
next求法跟上面基本一致,只需要把T当做目标串就好了,跟朴素KMP求next方法类似。注意next[1]=n,而且不能被p,a取。
回到题目
连ext都不用都不用,直接算出next加起来输出就行···
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=1000005;
int i,j,k,n,ts,last,q1,q2,x,next[N],a,p;
char s[N];
ll ans;
int main()
{
scanf("%s\n",s+1);
n=strlen(s+1);
next[1]=n;
for(i=1;i<n&&s[i]==s[i+1];i++);
next[2]=i-1;
a=2;
fo(i,3,n)
{
p=a+next[a]-1;
next[i]=next[i-a+1];
if (next[i]+i-1>=p)
{
next[i]=max(p-i+1,0);
while (i+next[i]<=n&&s[1+next[i]]==s[i+next[i]]) next[i]++;
a=i;
}
}
fo(i,1,n) ans+=next[i];
printf("%lld",ans);
}