半前缀计数
设小写字母字符串 (s), 长度为 (n), (s[l:r]) 表示第 (l) 个到第 (r) 个字符构成的子串, (l>r) 时对应空串。
定义半前缀是 (s[1:i] + s[j:k]), 其中 (0 leq i < len(s), i < j leq len(s),j-1 leq kleq len(s))。直观上来说,你可以把半前缀理解成某一个前缀 (s[1:k]) 删除掉某一个子串后形成的结果(当然也允许不删)。
给出字符串 (s),你需要求出 (s) 的所有半前缀中,有多少个不同的字符串。
题解
构造一个自动机,先贪心匹配前缀,再匹配后面的子串。
注意匹配了前缀(1sim i)后,之后的子串不能以(s_{i+1})开头。
乍看要写个Ukkonen算法,但是实际上自动机不用建出来。我们只需要知道(i+1sim n)的不同子串数量以及以(s_{i+1})开头的不同子串数量即可。
倒着建SAM,时间复杂度(O(n))。
CO int N=2e6;
int last=1,tot=1;
array<int,26> ch[N];
int fa[N],len[N];
void extend(int c){
int x=last,cur=last=++tot;
len[cur]=len[x]+1;
for(;x and !ch[x][c];x=fa[x]) ch[x][c]=cur;
if(!x) {fa[cur]=1; return;}
int y=ch[x][c];
if(len[y]==len[x]+1) {fa[cur]=y; return;}
int clone=++tot;
ch[clone]=ch[y],fa[clone]=fa[y],len[clone]=len[x]+1;
fa[cur]=fa[y]=clone;
for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
}
int64 sum,cnt[26];
char str[N];
int main(){
scanf("%s",str+1);
int n=strlen(str+1);
int64 ans=1;
for(int i=n;i>=1;--i){
int c=str[i]-'a';
extend(c);
sum+=len[last]-len[fa[last]],cnt[c]+=len[last]-len[fa[last]];
ans+=1+sum-cnt[c];
}
printf("%lld
",ans);
return 0;
}