【YbtOJ#20072】相似子串

题目

题目链接:http://noip.ybtoj.com.cn/problem/20072

思路

对于一个长度为 \(m\) 的询问串,显然要求的就是 \(s\) 中有多少个长度为 \(m\) 的区间和等于询问串的和。
考虑根号分治。假设所有询问串串长和为 \(t\)

  • \(m\leq \sqrt{t}\) 时,我们可以预处理 \(cnt[i][j]\) 表示 \(s\) 长度为 \(i\) 的区间有多少个和为 \(j\)。然后每次可以 \(O(1)\) 回答。
  • \(m>\sqrt{t}\) 时,显然这种询问次数不会超过 \(\frac{t}{\sqrt t}=\sqrt{t}\) 次,所以我们每次 \(O(n)\) 暴力做即可。

时间复杂度 \(O((n+m)\sqrt{t})\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=200010,M=450;
int Q,T,m,n,ans,sum,num,cnt[M][M];
char s[N],t[N];

int main()
{
	freopen("similar.in","r",stdin);
	freopen("similar.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1); T=sqrt(n);
	s[0]=48;
	for (int i=1;i<=T;i++)
	{
		sum=0;
		for (int j=1;j<i;j++) sum+=s[j]-48;
		for (int j=i;j<=n;j++)
		{
			sum=sum+(s[j]-48)-(s[j-i]-48);
			cnt[i][sum]++;
		}
	}
	scanf("%d",&Q);
	while (Q--)
	{
		scanf("%s",t+1);
		m=strlen(t+1); num=sum=0;
		for (int i=1;i<=m;i++) num+=t[i]-48;
		if (m<=T) printf("%d\n",cnt[m][num]);
		else
		{
			ans=0;
			for (int i=1;i<m;i++) sum+=s[i]-48;
			for (int i=m;i<=n;i++)
			{
				sum=sum+(s[i]-48)-(s[i-m]-48);
				if (sum==num) ans++;
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}
posted @ 2020-10-26 17:35  stoorz  阅读(67)  评论(0编辑  收藏  举报