Loading

LibreOJ #10047

应同机房某大佬的要求来写这篇题解

Description

给定一个字符串 \(S\) 和一个数 \(K\),要求求出 \(S\) 的所有形似 \(A+B+A\) 的子串数量,其中 \(\mid A\mid \ge K\)\(\mid B\mid \ge 1\)

位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一子串

Solution

\(S\) 的长度为 \(n\)

对于 \(S\)\(1\)\(n\) 的每一个位置,以当前位置的字符作为一个子串的起点,然后寻找它的终点,来确定当前子串是否满足条件

对于 \(Next\) 数组的预处理还是相同操作,但是每换一个起点就要重新处理一次,只处理从当前位置到最后的字符

对于每个子串的终点,可以发现,只要在满足总长度的情况下不停回溯,才能统计所有的情况

回溯到最后的位置就是与它相同的最初子串的结束位置,因为不同位置相同子串算作同一子串,所以遇到相同的时,回溯到第一个相同子串便可统计

同理,若第一个相同子串不满足长度限制,那么后面的必定也不满足长度限制

具体原理请参考 KMP 的 \(Next\) 数组的原理

最后再判断一下子串的相同前后缀长度是否满足大于 \(K\) 就好了

Code

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define maxn 1000010

using namespace std;

int Nxt[maxn],J,n,k;
int ans;
char s[maxn];

int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
	return s*w;
}

void Kmp(int Begin){
	J=Begin-1;Nxt[Begin]=Nxt[Begin-1]=J;
	for(int i=Begin;i<n;i++){
		while(J>Begin-1&&s[J+1]!=s[i+1]) J=Nxt[J];
		if(s[J+1]==s[i+1]) J++;
		Nxt[i+1]=J;
	}
	for(int i=Begin;i<n;i++){
		J=Nxt[i+1];//
		while(J>Begin-1&&Begin+2*(J-Begin+1)>i+1) J=Nxt[J];//
		if(J-Begin+1>=k) ans++;
	}
}

int main(){
	scanf("%s",s+1);k=read();
	n=strlen(s+1);Nxt[0]=Nxt[1]=1;
	for(int i=1;i<=n;i++) Kmp(i);
	printf("%d",ans);
	return 0;
}
posted @ 2021-01-07 21:47  KnightL  阅读(61)  评论(1编辑  收藏  举报