UOJ523 半前缀计数
半前缀计数
设小写字母字符串 \(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 k\leq len(s)\)。直观上来说,你可以把半前缀理解成某一个前缀 \(s[1:k]\) 删除掉某一个子串后形成的结果(当然也允许不删)。
给出字符串 \(s\),你需要求出 \(s\) 的所有半前缀中,有多少个不同的字符串。
题解
构造一个自动机,先贪心匹配前缀,再匹配后面的子串。
注意匹配了前缀\(1\sim i\)后,之后的子串不能以\(s_{i+1}\)开头。
乍看要写个Ukkonen算法,但是实际上自动机不用建出来。我们只需要知道\(i+1\sim 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\n",ans);
return 0;
}
静渊以有谋,疏通而知事。