题意简述
给你一个数字字符串 S S S,长度为 n < = 1 e 5 n<=1e5 n<=1e5。有一个 i n t int int范围内的质数 P P P和 Q < = 1 e 5 Q<=1e5 Q<=1e5次询问,每次询问一个区间 [ l , r ] [l,r] [l,r]中, S S S的多少个子串(不一定不同)是 P P P的倍数。
比如 S = " 007 " S="007" S="007",它有6个子串,分别是 { 0 , 0 , 00 , 07 , 007 } \{0,0,00,07,007\} {0,0,00,07,007},这 6 6 6个都是 7 7 7的倍数。
思路框架
处理后缀和,分类讨论 p p p和 10 10 10是否互质。一个用莫队维护,一个用树状数组维护。
具体思路
先分类讨论。
1. P和10互质
那么末尾多几个 0 0 0就不会影响对 P P P的整除性。
维护后缀和 s u f suf suf,它表示从 i i i开始的后缀接起来膜 P P P的值。特殊地, s u f [ n + 1 ] = 0 suf[n+1]=0 suf[n+1]=0。对于一个区间 [ l , r ] [l,r] [l,r],当 s u f [ l ] suf[l] suf[l]和 s u f [ r + 1 ] suf[r+1] suf[r+1]相等的时候, [ l , r ] [l,r] [l,r]这一段就是 P P P的倍数了。相当于我们要在 s u f suf suf数组中维护相等的无序对数,莫队即珂。
2. P不和10互质
即:P=2或5
这个时候我们就只需要判断末尾就好了。对于 2 2 2的情况,那么子串结尾就是 0 , 2 , 4 , 6 , 8 0,2,4,6,8 0,2,4,6,8。对于 5 5 5的情况,子串结尾就是 0 , 5 0,5 0,5。
假设询问 [ l , r ] [l,r] [l,r]。我们找到一个 k k k,如果以 k k k为结尾的子串一定满足条件,那么这个 k k k对答案的贡献就是 k − ( l − 1 ) k-(l-1) k−(l−1)。(因为开始位置珂以是 [ l , k ] [l,k] [l,k]中的任意一个,一共有 k − ( l − 1 ) k-(l-1) k−(l−1)种情况)
我们先对 2 2 2和 5 5 5的情况分开讨论,然后开 20 20 20个树状数组,其中 10 10 10个维护长度为0~9的下标的和,另外 10 10 10个维护 0 9 0~9 0 9中的数量和。
对于 [ l , r ] [l,r] [l,r]的询问,相当于要求和所有 k − ( l − 1 ) k-(l-1) k−(l−1)。令满足条件 k k k的数量是 C C C,和为 S S S。那么这一段答案就是 S − ( l − 1 ) × C S-(l-1)\times C S−(l−1)×C。对于每一种尾数累加答案即珂。
代码
#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;
}

博客介绍了如何解决一个关于大数子串与质数倍数关系的问题,涉及到后缀和、分类讨论(质数P与10是否互质)以及使用莫队和树状数组的数据结构进行维护。对于P与10互质的情况,通过比较后缀和寻找相等无序对;对于P等于2或5的情况,仅需关注子串的末尾数字。给出了具体的代码实现思路。
585

被折叠的 条评论
为什么被折叠?



