【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;
}