BZOJ4542: [Hnoi2016]大数

本文详细解析了BZOJ4542大数问题的解题思路,采用莫队算法和离散化处理解决区间内子串求和问题,并针对特殊情况p为2或5时提出解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BZOJ4542: [Hnoi2016]大数

莫队·乱搞

题解:

蛤~我这种蒟蒻怎么可能自己想出来呢2333~
但是我看网上的题解很多没有证明过程,就写一下吧~

ans=i=lrj=ir1((10)nrk=ijs[i]10nkmodp==0)

num[i]=jk=is[i]10nk
因为p是素数,那么10=10p2

原式:

ans=i=lrj=ir1(10(p2)(nr)(num[i]num[j+1])modp==0)

如果p!=2&&p!=510tmodp肯定不为0。所以要想modp==0,则必有num[i]=num[j+1].

这样就转化成了一个区间中有多少对数相同,离散化一下,用莫队就可以了。

那么假如p==2||p==5呢?

我们发现一个数只要最后一位是2或5的倍数,不管前面是什么,它一定是。因此只考虑最后一位。

做两个前缀和,一个(A)记录前缀[1,i]中有多少子串是p的倍数,另一个(B)记录前缀[1,i]中有多少位是p的倍数。

ans=(A[r]A[l1])(B[r]B[l1])(l1)

(B[r]B[l1])(l1)就是开始在l前面,结束在[l,r]中的p的倍数子串的个数。

好神啊QwQ……

Code:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
const int N = 100005;

ll p,n,m,res,bsz;  char str[N];
ll cnt[N],num[N],tp[N],ans[N],A[N],B[N]; 

struct Que{
    ll l,r,id;
    bool operator < (const Que &b) const {
        return l/bsz < b.l/bsz || (l/bsz == b.l/bsz && r < b.r);
    }
} q[N];

void insert(ll x){ res+=cnt[x]; cnt[x]++; }
void erase(ll x){ cnt[x]--; res-=cnt[x]; }

int main(){
    freopen("a.in","r",stdin);
    scanf("%lld%s%lld",&p,str+1,&m); 
    n=strlen(str+1);
    if(p!=2 && p!=5){
        ll base=1;
        for(ll i=n;i>=1;i--){
            base=base*10%p;
            num[i]=(num[i+1]+(str[i]-'0')*base)%p;
            tp[i]=num[i];
        } tp[n+1]=num[n+1];
        sort(tp+1,tp+2+n); ll tpsz=unique(tp+1,tp+2+n)-tp-1;
        for(ll i=1;i<=n+1;i++){
            num[i]=lower_bound(tp+1,tp+1+tpsz,num[i])-tp;
        }
        bsz=sqrt(n);
        for(ll i=1;i<=m;i++){
            scanf("%lld%lld",&q[i].l,&q[i].r);
            q[i].id=i; q[i].r++;
        }
        sort(q+1,q+1+m); 
        ll lpos=1, rpos=0;
        for(ll i=1;i<=m;i++){
            while(lpos < q[i].l){ erase(num[lpos]); lpos++; }
            while(lpos > q[i].l){ lpos--; insert(num[lpos]); }
            while(rpos < q[i].r){ rpos++; insert(num[rpos]); }
            while(rpos > q[i].r){ erase(num[rpos]); rpos--; }
            ans[q[i].id]=res;
        }
        for(ll i=1;i<=m;i++) printf("%lld\n",ans[i]);
    }
    else{
        for(ll i=1;i<=n;i++){ 
            if((str[i]-'0')%p==0){ A[i]=A[i-1]+i; B[i]=B[i-1]+1; }
            else{ A[i]=A[i-1]; B[i]=B[i-1]; }
        }
        ll l,r;
        for(ll i=1;i<=m;i++){
            scanf("%lld%lld",&l,&r);
            printf("%lld\n",(A[r]-A[l-1])-(B[r]-B[l-1])*(l-1));
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值