模拟赛 数根 题解

题意:
对于一个\(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;
}
posted @ 2021-04-06 19:54  lnzwz  阅读(115)  评论(0编辑  收藏  举报