[bzoj4542][HNOI2016]大数

题目大意

给定字符串
每次询问该字符串的一个子串中有多少子串转为数字后是p的倍数。
p<10^10且是质数,每次询问p相同。

式子转化

如果对[l,r]询问那么答案相当于
ri=lrj=i(jk=is[k]10jkmodp==0)
ri=lrj=i(10jjk=is[k](10)kmodp==0)
其中10’表示10关于p的逆元,由于p是质数,所以当p不为2或5时可以这么做。
p是2或5怎么做待会再说。
如果我们设 num[i]=nj=is[j](10)j
那么上面的式子又等于
ri=lrj=i(10j(num[i]num[j+1])modp==0)
因为p不为2或5,所以后面的如果要模p为0,必须有num[i]=num[j+1]
于是问题转化为:
询问[l,r+1]有多少对 l<=i<j<=r+1 满足num[i]=num[j]
离散化后用莫队算法即可。
现在我们来解决一下p=2或5的情况(虽然实际证明数据没有这样的点)
那么模p余0只有个位模p余0才行!
于是也可以上莫队,维护当前区间有多少模p余0的数即可。
注意爆long long的情况。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=100000+10;
int belong[maxn],s[maxn],cnt[maxn],ans[maxn];
ll num[maxn],b[maxn];
struct dong{
    int l,r,id;
} ask[maxn];
bool operator <(dong a,dong b){
    if (belong[a.l]<belong[b.l]) return 1;
    else if (belong[a.l]==belong[b.l]&&a.r<b.r) return 1;
    else return 0;
}
int i,j,k,l,r,n,m,c,now;
ll t,p,q;
char ch;
ll qsc(ll x,ll y){
    if (!y) return 0;
    ll t=qsc(x,y/2);
    t=(t+t)%p;
    if (y%2) t=(t+x)%p;
    return t;
}
ll qsm(ll x,int y){
    if (!y) return 1;
    ll t=qsm(x,y/2);
    t=qsc(t,t);
    if (y%2) t=qsc(t,x);
    return t;
}
void gcd(ll a,ll b,ll &x,ll &y){
    if (!b){
        x=1;
        y=0;
        return;
    }
    else{
        gcd(b,a%b,x,y);
        swap(x,y);
        y-=x*(a/b);
    }
}
ll getny(ll a,ll b){
    ll x,y;
    gcd(a,b,x,y);
    x=(x%b+b)%b;
    return x;
}
int main(){
    //freopen("number13.in","r",stdin);freopen("answer.out","w",stdout);
    scanf("%lld",&p);
    ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    s[n=1]=ch-'0';
    while (1){
        ch=getchar();
        if (ch<'0'||ch>'9') break;
        s[++n]=ch-'0';
    }
    s[++n]=0;
    c=floor(sqrt(n));
    fo(i,1,n) belong[i]=(i-1)/c+1;
    scanf("%d",&m);
    fo(i,1,m) scanf("%d%d",&ask[i].l,&ask[i].r),ask[i].r++,ask[i].id=i;
    //printf("%d\n",clock());
    sort(ask+1,ask+m+1);
    if (p!=2&&p!=5){
        t=1;
        fd(i,n,1){
            b[i]=num[i]=(ll)(num[i+1]+(ll)s[i]*t%p)%p;
            t=t*10%p;
        }
        sort(b+1,b+n+2);
        l=unique(b+1,b+n+2)-b-1;
        fo(i,1,n) num[i]=lower_bound(b+1,b+l+1,num[i])-b;
    }
    //printf("%d\n",clock());
    l=1;r=0;
    fo(i,1,m){
        while (l<ask[i].l){
            if (p==2){
                now-=t;
                if (s[l]%2==0) t--;
            }
            else if (p==5){
                now-=t;
                if (s[l]%5==0) t--;
            }
            else{
                cnt[num[l]]--;
                now-=cnt[num[l]];
            }
            l++;
        }
        while (l>ask[i].l){
            l--;
            if (p==2){
                if (s[l]%2==0) t++;
                now+=t;
            }
            else if (p==5){
                if (s[l]%5==0) t++;
                now+=t;
            }
            else{
                now+=cnt[num[l]];
                cnt[num[l]]++;
            }
        }
        while (r>ask[i].r){
            if (p==2){
                if (s[r]%2==0) now-=(r-l+1),t--;
            }
            else if (p==5){
                if (s[r]%5==0) now-=(r-l+1),t--;
            }
            else{
                cnt[num[r]]--;
                now-=cnt[num[r]];
            }
            r--;
        }
        while (r<ask[i].r){
            r++;
            if (p==2){
                if (s[r]%2==0) now+=(r-l+1),t++;
            }
            else if (p==5){
                if (s[r]%5==0) now+=(r-l+1),t++;
            }
            else{
                now+=cnt[num[r]];
                cnt[num[r]]++;
            }
        }
        ans[ask[i].id]=now;
    }
    fo(i,1,m) printf("%d\n",ans[i]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值