题意简述
给你一个数字字符串 ,长度为 。有一个 范围内的质数 和 次询问,每次询问一个区间 中, 的多少个子串(不一定不同)是 的倍数。
比如 ,它有6个子串,分别是 ,这 个都是 的倍数。
思路框架
处理后缀和,分类讨论 和 是否互质。一个用莫队维护,一个用树状数组维护。
具体思路
先分类讨论。
1. P和10互质
那么末尾多几个 就不会影响对 的整除性。
维护后缀和 ,它表示从 开始的后缀接起来膜 的值。特殊地, 。对于一个区间 ,当 和 相等的时候, 这一段就是 的倍数了。相当于我们要在 数组中维护相等的无序对数,莫队即珂。
2. P不和10互质
即:P=2或5
这个时候我们就只需要判断末尾就好了。对于 的情况,那么子串结尾就是 。对于 的情况,子串结尾就是 。
假设询问 。我们找到一个 ,如果以 为结尾的子串一定满足条件,那么这个 对答案的贡献就是 。(因为开始位置珂以是 中的任意一个,一共有 种情况)
我们先对 和 的情况分开讨论,然后开 个树状数组,其中 个维护长度为0~9的下标的和,另外 个维护 中的数量和。
对于 的询问,相当于要求和所有 。令满足条件 的数量是 ,和为 。那么这一段答案就是 。对于每一种尾数累加答案即珂。
代码
#include <bits/stdc++.h>using namespace std;
namespace Flandre_Scarlet
{
#define N 155555
#define int long long
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
int n,p;
char s[N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(p);scanf("%s",s+1);
n=strlen(s+1);
}
int a[N];
namespace SpecialCxk //p=2或5的情况
{
class BIT
{
public:
int tree[N];
int len;
void BuildTree(int _len)
{
len=_len;
FK(tree);
}
void Add(int pos,int val=1)
{
for(int i=pos;i<=len;i+=(i&(-i)))
{
tree[i]+=val;
}
}
int Query(int pos)
{
int ans=0;
for(int i=pos;i>0;i-=(i&(-i)))
{
ans+=tree[i];
}
return ans;
}
int RQuery(int l,int r)
{
return Query(r)-Query(l-1);
}
}T[10][2]; //T[i][j]: 值为i的所有位置的j次方和
//换句话说,j=0时计算数量,j=1时计算下标的和
void Soviet()
{
F(i,0,9) F(j,0,1) T[i][j].BuildTree(n+5);
if (p==2)
{
F(i,1,n)
{
T[a[i]][0].Add(i,1);
T[a[i]][1].Add(i,i); //对于第i个位置,一个加上1,一个加上i
//所以一个是数量和,一个是下标和
}
int q;R1(q);
F(i,1,q)
{
int l,r;
R1(l),R1(r);
int cnt0=T[0][0].RQuery(l,r);
int sum0=T[0][1].RQuery(l,r)-(l-1)*cnt0;
int cnt2=T[2][0].RQuery(l,r);
int sum2=T[2][1].RQuery(l,r)-(l-1)*cnt2;
int cnt4=T[4][0].RQuery(l,r);
int sum4=T[4][1].RQuery(l,r)-(l-1)*cnt4;
int cnt8=T[8][0].RQuery(l,r);
int sum8=T[8][1].RQuery(l,r)-(l-1)*cnt8;
//0 2 4 8分类讨论一下
printf("%lld\n",sum0+sum2+sum4+sum8); //累加
}
}
else if (p==5) //和p==2的情况没什么本质差别,是同样的思路
{
F(i,1,n)
{
T[a[i]][0].Add(i,1);
T[a[i]][1].Add(i,i);
}
int q;R1(q);
F(i,1,q)
{
int l,r;
R1(l),R1(r);
int cnt0=T[0][0].RQuery(l,r);
int sum0=T[0][1].RQuery(l,r)-(l-1)*cnt0;
int cnt5=T[5][0].RQuery(l,r);
int sum5=T[5][1].RQuery(l,r)-(l-1)*cnt5;
printf("%lld\n",sum0+sum5);
}
}
}
}
namespace Normal //p不是2或5的情况
{
int p10[N],suf[N];
map<int,int> disc;int dcnt=0;
int q,sn,ans[N];
struct node{int l,r,id;}Q[N];
bool cmp(node a,node b){return a.l/sn<b.l/sn or (a.l/sn==b.l/sn and a.r<b.r);}
int cur;
int cnt[N];
void Add(int x){cur+=cnt[x];cnt[x]++;}
void Del(int x){cnt[x]--;cur-=cnt[x];}
void Soviet()
{
p10[0]=1;F(i,1,n) p10[i]=p10[i-1]*10ll%p; //处理10的幂,方便维护suf数组
suf[n+1]=0; D(i,n,1) suf[i]=(suf[i+1]+p10[n-i]*a[i])%p; //求出后缀和数组suf
F(i,1,n+1)
{
if (!disc[suf[i]]) disc[suf[i]]=++dcnt;
suf[i]=disc[suf[i]];
} //把suf离散化,我用的是map离散化,懒得写排序+lowerbound了
//反正map也能过
sn=sqrt(n+0.5);
R1(q);
F(i,1,q)
{
int l,r;R1(l),R1(r);
Q[i]=(node){l,r+1,i}; //注意这里改成l,r+1
}
sort(Q+1,Q+q+1,cmp);
int ll=1,rr=0;cur=0;
F(i,1,q)
{
while(ll<Q[i].l) Del(suf[ll]),ll++;
while(rr>Q[i].r) Del(suf[rr]),rr--;
while(ll>Q[i].l) ll--,Add(suf[ll]);
while(rr<Q[i].r) rr++,Add(suf[rr]);
ans[Q[i].id]=cur;
} //莫队
F(i,1,q) printf("%lld\n",ans[i]);
}
}
void Soviet()
{
F(i,1,n) a[i]=s[i]-'0';
if (p==2 or p==5) SpecialCxk::Soviet();
else Normal::Soviet();
}
#define Flan void
Flan IsMyWife()
{
Input();
Soviet();
}
#undef int //long long
}
int main(){
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}