洛谷P3435 [POI2006]OKR-Periods of Words题解(KMP)

题目链接:https://www.luogu.com.cn/problem/P3435

洛谷P3435 [POI2006]OKR-Periods of Words

KMP
题意为求给定字符串所有前缀的最长真循环节长度之和。
fail函数的应用,给定字符串的最长真循环节长度即为字符串长度减去最短非空公共前后缀长度,先求出原字符串的fail函数值。再从1开始遍历,若fail[i]能够继续减小(即它不是最短的公共前后缀),就令当前位置j=fail[j],直到fail[j]==-1时j所在位置即为原字符串的最短公共前后缀,将fail[i]直接赋值,并继续往后遍历,由于前面步骤已经求出了fail[i]的最小值,后继步骤的循环查找次数不会过多(此处相当于记忆化搜索),最后遍历一遍所有前缀并加上对答案的贡献即可,但要注意特判最短公共前后缀长度为0的情况。

#include
#define next next_
#define y1 yy
#define hash hash_
#define complex complex_
using namespace std;

using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
using pll=pair<ll,ll>;

const ll mod=1e9+7;
const double pi=acos(-1.0);
int _;

ll n,fail[1000010];
string s;

void getfail(string s){
	ll len=s.size();
	fail[0]=-1;
	ll i,j;
	for(j=1;j<len;j++)
		for(i=fail[j-1];;i=fail[i]){
			if(s[j]==s[i+1]){
				fail[j]=i+1;
				break;
			}
			else if(i==-1){
				fail[j]=-1;
				break;
			}
		}
}

void work(){
	cin>>n>>s;
	getfail(s);
	for(ll i=1;i<n;i++){
		ll j=i;
		while(fail[j]!=-1) j=fail[j];
		if(fail[i]!=-1) fail[i]=j;
	}
	ll ans=0;
	for(ll i=0;i<n;i++){
		if(fail[i]==-1) continue;
		ans+=(i+1)-fail[i]-1;
	}
	printf("%lld",ans);
}

int main(){
//	scanf("%d",&_);
	_=1;
	while(_--){
		work();
	}
	return 0;
}

你可能感兴趣的:(KMP,洛谷跳题)