KMP简单应用

KMP简单应用

算法原理就不讲了,抄书没有意思请原谅我讲不清楚

下面给出几个\(next\)数组的性质:

  1. \(i-next_i\)为最小循环元长度(不论是否能够完整循环)、

  2. \(next_i\)再套若干层\(next\)仍然满足\([1,j]=[i-j+1,i]\)

证明这些在下面的题里面

Power Strings

所求的循环元长度为\(i-next_i\),其循环次数为\(\frac{i}{i-next_i}\),特别的,\(i=next_i\)时,整个字符串就是循环元、

证明:

根据循环元的性质:\([1,next_i]=[i-next_i+1,i]\),整体减去\([i-next_i+1,next_i]\),可以得到\([1,i-next_i]=[i-next_i+1,2i-2next_i]\)。以此类推可得整个字符串都被覆盖掉了

	if(m%(m-nxt[m]))cout<<"1\n";
	else cout<<m/(m-nxt[m])<<"\n";	

练习题:Period

最长前缀

考虑转化问题。

首先对集合中的每一个串对源字符串进行匹配,就能得到所有的匹配成功区间\([l_i,r_i]\)

然后问题就化为:给定若干区间,求其最长能够覆盖区间的\([1,i]\)

\(f_i\)表示\([1,i]\)能否被覆盖,将所有区间以右端点为第一关键字,左端点为第二关键字排序,使用双指针扫描法,使得当前区间\(k\)满足\(r_k=i\),则\(f_i=f_i|f_{l_k-1}\)

void init(int id){
	int m=strlen(a[id]+1);
	for(int i=2,j=0;i<=m;i++){
		while(j&&a[id][j+1]!=a[id][i])j=nxt[id][j];
		if(a[id][j+1]==a[id][i])++j;
		nxt[id][i]=j;
	}
	for(int i=1,j=0;i<=n;i++){
		while(j&&b[i]!=a[id][j+1])j=nxt[id][j];
		if(b[i]==a[id][j+1])++j;
		if(j==m){
			g[++num]={i-m+1,i};j=nxt[id][j];
		}
	}
}
void get(){
	while(cin>>a[++cnt]+1){
		if(a[cnt][1]=='.'){
			--cnt;break;
		}
	}
	while(cin>>b[++n]);
}
	get();
	for(int i=1;i<=cnt;i++)init(i);
	int l=1;
	f[0]=1;int ans=0;
	sort(g+1,g+num+1);
	for(int r=1;r<=n;r++){
		while(l<=num&&g[l].r<=r){
			f[r]|=f[g[l].l-1];++l;
		}
		if(f[r])ans=r;
	}

Seek the Name, Seek the Fame

容易发现,对于\(i\)来说,\(next\)数组的定义即\([1,next_i]=[i-next_i+1,i]\)

\(next_i\)就是最大长度。根据\(next\)的性质,答案显然是对其嵌套\(next\)即可

		int k=m,cnt=0;
		while(k){
			num[++cnt]=k;
			k=nxt[k];
		}
		while(cnt){
			cout<<num[cnt--]<<" ";
		} 

字符串大师

根据Power Strings,容易知道\(i-next_i=pre_i\),所以\(next_i=i-pre_i\)。故可以求出\(next\)数组。

因为字典序最小,第一个字符肯定是a

然后考虑进行递推,设已经求出了\(1\sim i-1\)的字符,现在求解\(i\)

那么若\(next_i\neq 0\),则等于\(a[i]=a[next_i]\)

否则的话,根据\(next\)的定义,有\(a[next_{i-1}+1]\neq a[i]\),因为失配了嘛在这里

而且类比上一题,也是进行嵌套的都不行,也即:

		int num=nxt[i-1];
		while(num){
			usd[a[num+1]-'a']=1;num=nxt[num];//这个位置不等
		}

然后一个需要注意的点是,这个地方不能重复取开头的字符a,原因是\(next_i=0\)

所以再枚举字符取最小就行

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		nxt[i]=i-x;
	}	
	a[1]='a';cout<<'a';
	for(int i=2;i<=n;i++){
		memset(usd,0,sizeof usd);
		if(nxt[i]!=0){a[i]=a[nxt[i]];cout<<a[i];continue;}
		int num=nxt[i-1];
		while(num){
			usd[a[num+1]-'a']=1;num=nxt[num];
		}
		for(int j=1;j<=25;j++){
			if(!usd[j]){
				a[i]=j+'a';
				cout<<a[i];break;
			}
		}
	}
} 

基因改造

人类智慧

容易发现,本质上在这里,C是啥用没有的。。。

然后在这个定义的匹配规则中,等价于要字符串的结构一样才行。

也即给字符建立映射用字符集重组只会一样。

举个例子:1113221与4447664是本质相同的,重组之后都可以得到1112331

那么对于结构相同的字符,字符相对关系不变。也即任意一类字符都只会整体改变,则其距离始终不变

同理,显然知道了每一类字符的距离也就可以求出一个本质相同的字符串

那么就很明晰了,定义其权值为\(i-pos[i]\),其中\(pos\)\(i\)上一个出现的位置。特别的,不存在就令其等于0。

则两个位置\(i,j(i<j)\)的充要条件是:\(val[i]=val[j]\)或者\(val[j]=0\)\(val[i]>j\)。理解很简单吧,要么是真的相等,要么就是长度为\(j\)意义下两个都为0.

使用KMP匹配即可。

	cin>>t>>c; 
	while(t--){
		num=0;
		cin>>n>>m;
		memset(pos,0,sizeof pos);
		for(int i=1;i<=n;i++){
			int x;cin>>x;
			va[i]=(pos[x]?i-pos[x]:0);pos[x]=i; 
		}
		memset(pos,0,sizeof pos);
		for(int i=1;i<=m;i++){
			int x;cin>>x;
			vb[i]=(pos[x]?i-pos[x]:0);pos[x]=i; 
		}
		for(int i=2,j=0;i<=m;i++){
			while(j&&vb[i]!=vb[j+1]&& !(vb[j+1]==0&&vb[i]>j))j=nxt[j];
			if(vb[i]==vb[j+1]||(vb[j+1]==0&&vb[i]>j))++j;
			nxt[i]=j;
		}
		for(int i=1,j=0;i<=n;i++){
			while(j&&(va[i]!=vb[j+1]&& !(vb[j+1]==0&&va[i]>j)))j=nxt[j];
			if(va[i]==vb[j+1]||(vb[j+1]==0&&va[i]>j))++j;
			if(j==m){
				ans[++num]=i-m+1;j=nxt[j];
			}
		}
		cout<<num<<"\n";
		for(int i=1;i<=num;i++)cout<<ans[i]<<" ";
		cout<<"\n";
	}

似乎在梦中见过的样子

注意到\(n\le 1.5\times 10^4\),\(n^2\)可以过

那么对于\(A+B+A\)形串,可以想起Seek the Name, Seek the Fame题,只要长度\(m<len-m\)就合法

这启发我们枚举后缀,求\(next\),然后对于每个后缀来说,枚举其每一个\(next\),用Seek the Name, Seek the Fame的方法,求出\(m\ge k\)的数量即可。

注意到

位置相同但拆分不同的子串算同一子串

则对于一个位置找到了就马上break

void init(){
	cin>>a+1>>k;n=strlen(a+1);
}
void get(int st){
	tot=0;
	for(int i=st;i<=n;i++)b[++tot]=a[i];
	for(int i=2,j=0;i<=tot;i++){
		while(j&&b[j+1]!=b[i])j=nxt[j];
		if(b[j+1]==b[i])++j;
		nxt[i]=j;
	}
}
void solve(int tot){
	for(int i=1;i<=tot;i++){
		int m=nxt[i];
		while(m>=k){
			if(i-2*m>=1){
				ans++;break;
			}
			m=nxt[m];
		}
	}
}
int main(){
	init();
	for(int i=1;i<=n;i++){
		get(i);
		solve(tot);
	}
	cout<<ans<<"\n";
}
posted @ 2023-03-05 07:38  spdarkle  阅读(11)  评论(0编辑  收藏  举报