版权声明:本文为博主原创文章,欢迎转载。 https://blog.csdn.net/try__jhf/article/details/82960974
题目大意
给出一个长度为 的数字串和一个素数 , 组数据求一段子串内为 倍数的子串个数。
解题分析
先假设 不为2和5,那么对于一个 的倍数 和一个位数为 的数字 ,那么有
所以如果可以弄一个后缀和,那么发现如果 和 关于 同余,那么子串 是p的倍数,所以题目变为找一段区间内 相等的对数。然后可以离线,明显莫队……
如果 为2或5,那么可以直接查找,因为只要一个子串的最后一位是 的倍数,那么这个数就是 的倍数。所以只要有一个数,那么从以这个点为右端的所有的这个点到左端点之间的数都是 的倍数。直接 预处理再 输出就行。
示例代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
int n,m,p,S,ha[100005],hsh[100005];
LL sum[100005],b[100005],k,ans[100005];
char s[100005];
struct data{
int L,R,id,num;
data (int L=0,int R=0,int id=0):L(L),R(R),id(id){num=S?(L-1)/S:0;}
bool operator < (const data b)const{return num<b.num||(num==b.num&&R<b.R);}
}a[100005];
inline void readi(int &x){
x=0; char ch=getchar();
while ('0'>ch||ch>'9') ch=getchar();
while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void _init(){
freopen("bignum.in","r",stdin);
freopen("bignum.out","w",stdout);
readi(p); scanf("%s",s+1); n=strlen(s+1); readi(m); S=sqrt(n);
for (int i=1,x,y;i<=m;i++){readi(x); readi(y); a[i]=data(x,++y,i);}
}
void _worka(){
for (int i=1;i<=n;i++)
{int pd=((s[i]-'0')%p==0); hsh[i]=hsh[i-1]+pd; sum[i]=sum[i-1]+pd*i;}
for (int i=1;i<=m;i++)
ans[i]=sum[a[i].R-1]-sum[a[i].L-1]-(a[i].L-1)*(hsh[a[i].R-1]-hsh[a[i].L-1]);
}
void _workb(){
sort(a+1,a+m+1); sum[n+1]=k=0; memset(ha,0,sizeof(ha));
LL f=1; for (int i=n;i;i--){b[i]=sum[i]=(sum[i+1]+f*(s[i]-'0')%p)%p; f=f*10%p;}
b[n+1]=0; sort(b+1,b+n+2); b[0]=unique(b+1,b+n+2)-b-1;
for(int i=1;i<=n+1;i++) sum[i]=lower_bound(b+1,b+1+b[0],sum[i])-b;
int L=1,R=0;
for (int i=1;i<=m;i++){
while (R<a[i].R) k+=ha[sum[++R]]++;
while (L>a[i].L) k+=ha[sum[--L]]++;
while (L<a[i].L) k-=--ha[sum[L++]];
while (R>a[i].R) k-=--ha[sum[R--]];
ans[a[i].id]=k;
}
}
void _solve(){
if (p==2||p==5) _worka(); else _workb();
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
int main()
{
_init();
_solve();
return 0;
}