其他分享
首页 > 其他分享> > xsy2318. Circular Shift

xsy2318. Circular Shift

作者:互联网

给定一个字符串 \(s\)。现在问你有多少个本质不同的 \(s\) 的子串 \(t=t_1t_2\ldots t_m(m>0)\) 使得将 \(t\) 循环左移一位后变成的 \(t′=t_2\ldots t_mt_1\) 也是 \(s\) 的一个子串。
\(1\le |s|\le 3\cdot 10^5\)。


首先看到本质不同的子串,容易想到用 \(\text{SAM}\) 解决。循环左移一位相当于在字符串 \(t_2\ldots t_m\) 的前后加入同样字符 \(t_1\)。

于是可以枚举字符串 \(t_2\ldots t_m\),接下来的问题就是计算有多少对前驱和后继一样。

如上图,考虑 \(\text{SAM}\) 上的一个节点 \(u\) 对应的 \(\text{endpos}\) 集合(银灰色线),黄色部分是长度为 \(len_{fa_u}\) 的父亲节点的最长后缀,蓝色部分是长度为 \(len_u-len_{fa_u}\) 的当前节点的最长后缀比父亲节点的最长后缀多出的部分,红色部分是当前节点左端点 \(-1\) 的位置,绿色部分是当前节点右端点 \(+1\) 的位置。相同的对数则是每个 红蓝部分 中与 绿色部分 一样的字符数量之和。

根据 \(\text{SAM}\) 的性质,每个 蓝黄部分 是一模一样的。于是可以前缀和每种字母出现的次数,算出 任意一个蓝色部分 与 每个绿色部分 一样的字符数量。剩下就是要算红色部分与绿色部分相同的对数,这个在枚举每个节点时判断它的父亲的最长后缀的前驱(红色部分)与后继(绿色部分)是否一样即可(因为叶子节点的最长后缀不可能再在前面加入字符,也就没有前驱,自然就不可能与后继匹配)。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,sum[N][26];long long ans;char s[N];
int last=1,tot=1;
struct Node{
	int son[26],len,fa,endpos;
	Node(){memset(son,0,sizeof son),len=fa=endpos=0;}
}node[N<<1];
inline void add(int c,int o){
	int p=last,nw=last=++tot;
	node[nw].endpos=o,node[nw].len=node[p].len+1;
	for(;p&&!node[p].son[c];p=node[p].fa)node[p].son[c]=nw;
	if(!p)node[nw].fa=1;
	else{
		int q=node[p].son[c];
		if(node[q].len==node[p].len+1)node[nw].fa=q;
		else{
			int xq=++tot;node[xq]=node[q];
			node[xq].len=node[p].len+1,node[nw].fa=node[q].fa=xq;
			for(;p&&node[p].son[c]==q;p=node[p].fa)node[p].son[c]=xq;
		}
	}
}
int main(){
	scanf("%s",s+1),n=strlen(s+1);
	for(int i=1;i<=n;++i)add(s[i]-'a',i);
	for(int i=1;i<=n;++i)
		for(int j=0;j<26;++j)
			sum[i][j]=sum[i-1][j]+(s[i]-'a'==j);
	for(int i=2;i<=tot;++i){
		if(node[node[i].fa].son[s[node[i].endpos-node[node[i].fa].len]-'a'])++ans;
		for(int j=0;j<26;++j)if(node[i].son[j])
			ans+=sum[node[i].endpos-node[node[i].fa].len-1][j]-sum[node[i].endpos-node[i].len][j];
	}
	printf("%lld\n",ans);
	return 0;
}

标签:ldots,后缀,Shift,len,xsy2318,text,部分,节点,Circular
来源: https://www.cnblogs.com/Samsara-soul/p/16360956.html