Tavas and Malekas 的题解

传送门

题目大意

文本串长度为 \(n\),给你模式串在文本串中出现的 \(m\) 个位置(必须在这些位置有出现过,也可以在其他位置出现),求可能的文本串数量,其中 \(1\le n \le 10^6\) 而且 \(0 \le m \le n-|p|+1\)

思路

简化

首先我们可以考虑如果输入保证合法,那么因为字符串都是小写字母,所以答案就是 \(26^{num}\)\(num\) 指没有被标记过的数量。
但是如果将访问过的位置全部标记出来最坏的情况下会多次重复的标记应景标记的位置,所以还是会超时。这个时候我们可以考虑优化。
因为输入的位置保证单调增,所以在标记的一开始就可以从 \(\max(a_i,a_{i-1}+len)\) 开始,有效避免重复标记。

分析

如果我们需要判断合法的话,那么我们就需要将重复的部分依次比对。
所以将时间复杂度拖慢的主要部分就是比较,所以考虑将比较优化了,可以使用字符串哈希。
因为我们比较的都是上一个字符串的后缀和这一个字符串的前缀,所以只需要比较前缀与后置哈希就可以了。

首先,我们先预处理出 \(p\) 数组,其中 \(p_i\) 表示 \(113^i\) 次方,注意这里的 \(113\) 就是哈希秘钥。

p[0]=1;
for(int i=1;i<=len;i++) p[i]=(p[i-1]*113)%mod;

接着,处理处字符串的前缀数组。

for(int i=1,v=s[i]-'a';i<=len;i++,v=s[i]-'a') front[i]=(front[i-1]*113+v)%mod;

最后将后缀数组也处理了。
注意因为要与前缀相比较,所以哈希值应该与前缀的顺序相同,也就是需要倒序枚举。

for(int i=len,v=s[i]-'a';i>=1;i--,v=s[i]-'a') back[i]=(back[i+1]+v*p[len-i])%mod;

接着在循环中就只需要判断前缀哈希与后缀哈希是否相同就好了。

AC Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,mod=1e9+7,INF=1e18;
char ch;
int ksm(int a,int b){ //求解 a^b
	int ans=1;
	while(b){
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}return ans;
}
int n,m,a[N]={-INF}; //将 a[0] 设为极小值,保证第一次覆盖完全
char s[N];
int front[N],back[N],p[N],len,ans;
bool vis[N];
signed main(){
	cin>>n>>m;
	cin>>s+1,len=strlen(s+1);
	p[0]=1;
	for(int i=1;i<=len;i++) p[i]=(p[i-1]*113)%mod;
	for(int i=1,v=s[i]-'a';i<=len;i++,v=s[i]-'a') front[i]=(front[i-1]*113+v)%mod;
	for(int i=len,v=s[i]-'a';i>=1;i--,v=s[i]-'a') back[i]=(back[i+1]+v*p[len-i])%mod;
	for(int i=1;i<=m;i++){
		cin>>a[i];
		if(a[i-1]+len>a[i]&&back[a[i]-a[i-1]+1]!=front[a[i-1]+len-a[i]]) cout<<0,exit(0);
		for(int j=max(a[i],a[i-1]+len);j<a[i]+len;j++) vis[j]=1;
	}for(int i=1;i<=n;i++) ans+=(!vis[i]);
	cout<<ksm(26,ans)<<endl;
	return 0;
}
posted @ 2024-07-14 13:34  未抑郁的刘大狗  阅读(2)  评论(0编辑  收藏  举报