模拟赛 数根 题解
题意:
对于一个\(B\)进制数\(x\)定义它的数根为把所有位上的数加在一起的结果。
给出一个长度为\(n\)的\(B\)进制串\(s\),\(m\)次询问每次给定一个集合\(A\)和一个数\(x\),计算有多少个\(s[l ... r]\)可以通过将至多一个字符更改为\(A\)中的元素使得数根为\(x\)。
\(n,m\leq 2^{20},B<=16\),时间限制8s。
首先,考虑快速求一个数的数根\(f(x)\)。
设\(B\)进制数\(x=\sum_{i=0}^{n}a_iB^i\),\(y=\sum_{i=0}^{n}a_i\)。那么\(y-x=\sum_{i=0}^{n}a_i(B^i-1)\)。由等比数列求和公式,\(B-1|B^i-1\)。因此\(x\equiv y \mod B-1\),即\(x\equiv f(x) \mod B-1\)。
那么维护总和模\(B-1\)为\(x\)的区间数量,就可以处理不进行修改的情况。
对于修改操作,假设一个区间原来的总和模\(B-1\)为\(y\),那么只要把其中的一个数字增加\(t=x-y\),就能满足条件。
设给出的集合为\(A\),再设\(C=\{x|x+t\in A\}\),那么只要这个总和为\(y\)的区间内元素的集合与\(C\)的交集不为空,这个区间就满足条件。
我们只要对于所有\(0\leq y<B-1\)和所有集合\(S\)求出总和为\(y\),区间内元素集合为\(S\)的区间数目,对于交集不为空这个条件用\(FMT\)预处理一下就行。
对于这个子问题,我们可以枚举右端点\(r\),并对于每个\(x\)维护从\(r\)往左第一个等于\(x\)的位置。这些位置把序列分为了若干段,每一段的\(S\)都是相等的。
对于每一段分别求即可。
这个使用前缀和即可优化至\(O(nB^2)\)。
现在我们已经解决了大部分情况,但是还有几个细节:
首先,对于\(x=0\)的询问,要求区间内都是0。这个我们可以维护全是\(0\)的以及只有一个非零位置的区间个数来计算。
对于\(x=B-1\)的询问,需要考虑把所有数字都改为0的情况。
若\(B-1\notin A\),那么全是0的需要减去。同时若\(0\in A\),那么长度为1的元素不为0的区间应当减去。
并且对于只有一个非零\(x\)的长度至少为2的区间,若\(B-1-x \notin A\),那么这个也得减去。
总时间复杂度为\(O((n+m)*B^2)\)。能过。
参考代码:
#include <stdio.h>
#include <string.h>
#define ll long long
char zf[1048580];int ch[300],su[1048580];
int sz[1048580],la[16],he[16][1048580];
void swap(int &a,int &b)
{
int t=a;a=b;b=t;
}
void fmt(ll sz[32768],int B)
{
for(int j=0;j<B;j++)
{
for(int i=0;i<(1<<B);i++)
{
if(i&(1<<j))
sz[i]+=sz[i^(1<<j)];
}
}
}
ll ss[16][32768],s1[16];int cs[16];
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
int n,m,B;ll s0=0;
scanf("%d%d%d%s",&n,&m,&B,zf);
for(int i=0;i<10;i++)
ch[i+'0']=i;
for(int i=0;i<6;i++)
ch[i+'a']=i+10;
he[0][0]=1;
for(int i=1;i<=n;i++)
{
sz[i]=ch[zf[i-1]];
cs[sz[i]]+=1;
su[i]=(su[i-1]+sz[i])%(B-1);
for(int j=0;j<B-1;j++)
he[j][i]=he[j][i-1];
he[su[i]][i]+=1;
}
for(int r=1,l1=0,l2=0;r<=n;r++)
{
if(sz[r]){l1=l2;l2=r;}
s0+=r-l2;
if(l2)s1[sz[l2]]+=l2-l1-(l2==r);
la[sz[r]%(B-1)]=r;int px[17],pz[17];
for(int i=0;i<B-1;i++)
px[i]=la[i],pz[i]=i;
px[B-1]=0;
for(int i=0;i<B-1;i++)
{
for(int j=i+1;j<B-1;j++)
{
if(px[i]<px[j])
swap(px[i],px[j]),swap(pz[i],pz[j]);
}
}
for(int i=0,z=0;i<B-1&&px[i];i++)
{
z|=(1<<pz[i]);
for(int j=0;j<B-1;j++)
{
int t=(su[r]-j+B-1)%(B-1);
ss[j][z]+=he[t][px[i]-1];
if(px[i+1])ss[j][z]-=he[t][px[i+1]-1];
}
}
}
for(int i=0;i<B-1;i++)fmt(ss[i],B-1);
for(int i=0;i<m;i++)
{
char xx[2],zz[20];bool bk[16]={0};
scanf("%s%s",xx,zz);
int x=ch[xx[0]],s=strlen(zz);
for(int j=0;j<s;j++)bk[ch[zz[j]]]=true;
if(x==0)
{
ll ans=s0;
if(bk[0])
{
for(int j=1;j<B;j++)
ans+=cs[j]+s1[j];
}
printf("%lld\n",ans);
continue;
}
ll ans=ss[x%(B-1)][(1<<(B-1))-1];
for(int y=0;y<B-1;y++)
{
if(x%(B-1)==y)continue;
int t=(x-y+B-1)%(B-1),z=0;
for(int j=0;j<s;j++)
z|=(1<<((ch[zz[j]]-t+B-1)%(B-1)));
ans+=ss[y][(1<<(B-1))-1];
ans-=ss[y][((1<<(B-1))-1)^z];
}
if(x==B-1&&!bk[B-1])
{
ans-=s0;
if(bk[0])
{
for(int j=1;j<B-1;j++)
{
ans-=cs[j];
if(!bk[B-1-j])ans-=s1[j];
}
}
}
printf("%lld\n",ans);
}
return 0;
}