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;
}

posted on 2020-07-05 19:54  autoint  阅读(62)  评论(0编辑  收藏

导航