洛谷3245 BZOJ4542 [HNOI2016]大数

题目描述

小 B 有一个很大的数 S,长度达到了 N 位;这个数可以看成是一个串,它可能有前导 0,例如00009312345。小B还有一个素数P。现在,小 B 提出了 M 个询问,每个询问求 S 的一个子串中有多少子串是 P 的倍数(0 也是P 的倍数)。例如 S为0077时,其子串 007有6个子串:0,0,7,00,07,007;显然0077的子串007有6个子串都是素数7的倍数。

输入输出格式

输入格式:

第一行一个整数:P。第二行一个串:S。第三行一个整数:M。接下来M行,每行两个整数 fr,to,表示对S 的子串S[fr…to]的一次询问。注意:S的最左端的数字的位置序号为 1;例如S为213567,则S[1]为 2,S[1…3]为 213。N,M<=100000,P为素数

输出格式:

输出M行,每行一个整数,第 i行是第 i个询问的答案。

思路

之前做过的题,今天在看到还是不会QAQ。痛下决心要把它搞懂。

定义a数组是字符串的*后缀和(类似哈希)。

对于P的倍数,可以得到等式:

a [ L ] − a [ R + 1 ] ∗ 1 0 R − L + 1 ≡ 0    ( m o d   P ) a[L]-a[R+1]*10^{R-L+1}\equiv0\ \ (mod\ P) a[L]a[R+1]10RL+10  (mod P)


a [ L ] ≡ a [ R + 1 ] ∗ 1 0 R − L + 1    ( m o d   P ) a[L]\equiv a[R+1]*10^{R-L+1}\ \ (mod \ P) a[L]a[R+1]10RL+1  (mod P)

P ≠ 2 P\neq 2 P̸=2 P ≠ 5 P\neq 5 P̸=5时, 1 0 L 10^{L} 10L有乘法逆元,得到

a [ L ] ∗ 1 0 L ≡ a [ R + 1 ] ∗ 1 0 R + 1 a[L]*10^{L}\equiv a[R+1]*10^{R+1} a[L]10La[R+1]10R+1

然后题目就变成求数组中相同数字的个数,用莫队搞定。 P = 2 P=2 P=2 P = 5 P=5 P=5时特判一下就好了。

代码

#include<map>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
const int maxn=5e5+5;
char s[maxn];
std :: map <long long,int> mp;
int P,N,Q,L,R,cnt,siz,blk[maxn],V[maxn];
long long ans,bas,Ans[maxn],sum[maxn],hsh[maxn];
struct node {int L,R,id;} a[maxn];
inline bool operator <(node a,node b) {
	return blk[a.L]^blk[b.L]?a.L<b.L:(blk[a.L]&1?a.R<b.R:a.R>b.R);
	//学长教的莫队小优化,无法优化复杂度,但效果立竿见影
}
inline int read() {
    int ret=0,f=1,ch=getchar();
    for (; !isdigit(ch); ch=getchar()) if (ch=='-') f=-f;
    for (; isdigit(ch); ch=getchar()) ret=ret*10+ch-48;
    return ret*f;
}
inline void revise(int pos,int add) {
    ans-=hsh[V[pos]]*(hsh[V[pos]]-1)/2;
    hsh[V[pos]]+=add;
    ans+=hsh[V[pos]]*(hsh[V[pos]]-1)/2;
}
inline void Solve() {
    for (int i=1; i<=N; ++i)
        Ans[i]=Ans[i-1]+((s[i]-48)%P==0)*i,sum[i]=sum[i-1]+((s[i]-48)%P==0);
    for (int Q=read(),L,R; Q--; )
        L=read(),R=read(),printf("%d\n",Ans[R]-Ans[L-1]-(sum[R]-sum[L-1])*(L-1));
}
int main() {
    register int i;
    scanf("%d\n%s",&P,s+1),N=strlen(s+1),siz=sqrt(N);
    if (P==2||P==5) return Solve(),0;
    for (i=1; i<=N+1; ++i) blk[i]=(i-1)/siz+1;
    for (bas=1,i=N; i; --i,(bas*=10)%=P) {
        (sum[i]=sum[i+1]+bas*(s[i]-48)%P)%=P;
        if (!mp[sum[i]]) mp[sum[i]]=++cnt;
    }
    if (!mp[sum[N+1]]) mp[sum[N+1]]=++cnt;
    for (int i=1; i<=N+1; ++i) V[i]=mp[sum[i]];
    for (Q=read(),i=1; i<=Q; ++i) a[i]=(node){read(),read()+1,i};
    std::sort(a+1,a+1+Q);
    for (R=ans=0,L=1,i=1; i<=Q; ++i) {
        while (L<a[i].L) revise(L++,-1);
        while (L>a[i].L) revise(--L,+1);
        while (R<a[i].R) revise(++R,+1);
        while (R>a[i].R) revise(R--,-1);
        Ans[a[i].id]=ans;
    }
    for (i=1; i<=Q; ++i) printf("%lld\n",Ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值