【HNOI 2016】大数

【HNOI 2016】大数

Problem

Description

小 B 有一个很大的数 \(S\),长度达到了 \(N\) 位;这个数可以看成是一个串,它可能有前导 \(0\),例如 00009312345 。小 B 还有一个素数 \(P\)

现在,小 B 提出了 \(M\) 个询问,每个询问求 \(S\) 的一个子串中有多少子串是 \(P\) 的倍数(\(0\) 也是 \(P\) 的倍数)。例如 \(S\)0077 时,其子串 007 有六个子串:0, 0, 7, 00, 07, 007;显然 0077 的子串 077 的六个子串都是素数 \(7\) 的倍数。

Input Format

第一行一个整数:\(P\)

第二行一个串:\(S\)

第三行一个整数:\(M\)

接下来 \(M\) 行,每行两个整数 \(\text{fr}, \text{to}\),表示对 \(S\) 的子串 \(S[\text{fr} \ldots \text {to}]\) 的一次询问。

注意:\(S\) 的最左端的数字的位置序号为 \(1\);例如 \(S\)\(213567\),则 \(S[1]\)\(2\)\(S[1 \ldots 3]\)\(213\)

Output Format

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

Sample

Input

11
121121
3
1 6
1 5
1 4

Output

5
3
2

Explanation

Explanation for Input

第一个询问问的是整个串,满足条件的子串分别有:121121211211121121

Range

对于所有的数据,\(N,M \leq 100000\)\(P\) 为素数。

Algorithm

莫队

Mentality

\(......\) 比较送分的题目。

支持 \(nlog\) 的数据范围,可离线的区间询问,且问的内容一看就可以莫队 = = 。

我们发现,设 \(r[i]\)\([i,n]\) 组成的数,那么有:

\[ num(i,j)=\frac{(r[i]-r[j+1])}{10^{n-j}} \]

我们把分母移到左边去,那么便有:

\[ num(i,j)\cdot 10^{n-j}=r[i]-r[j+1]\\ num(i,j)\cdot 10^{n-j}\ mod\ P=(r[i]-r[j+1])\ mod\ P \\ num(i,j)\cdot 10^{n-j}\ mod\ P=r[i]\ mod\ P-r[j+1]\ mod\ P \]

那么,当 \(r[i]\)\(r[j+1]\)\(P\) 取模的余数相同,那等式就必定等于 \(0\) ,则有:

\[ num(i,j)\cdot 10^{n-j}\ mod\ P=0 \]

那么当 \(10^{n-j}\ mod\ P\neq 0\) 时,因为最后的结果为 \(0\) ,所以必有 \(num(i,j)\ mod\ P=0\)

也就是说,当 \(P\neq 2,5\) 时,\(num(i,j)\)\(P\) 的倍数当且仅当 \(r[i]\)\(r[j+1]\) 关于模数 \(P\) 同余。

那么我们的问题就变成了一个区间内有多少属性相同的点对了,这个就很莫队了。

至于 \(P=2,5\) 的时候,特判处理即可。我们可以直接通过判断一个数的末位来判断这个数是否为 \(P\) 的倍数,我们可以直接记前缀和哇。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,mod,size,rest[100002],L,R,cnt,now[100001],ano[10][100001],sum[100001];
char S[100001];
long long ans,answer[100001],q[100001];
struct Que{int l,r,q,d;}k[100001];
struct node{int d,rest;}ls[100002];
bool cmp(Que a,Que b){return a.q==b.q?a.r<b.r:a.q<b.q;}
bool cmp2(node a,node b){return a.rest<b.rest;}
void Del(int x){ans-=--now[rest[x]];}
void Add(int x){ans+=now[rest[x]]++;}
int main()
{
    cin>>mod>>S>>m;
    n=strlen(S);
    size=sqrt(n);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&k[i].l,&k[i].r);
        k[i].q=k[i].l/size;
        k[i].r++;
        k[i].d=i;
    }
    if(mod==2||mod==5)
    {
        for(int i=n;i>0;i--)
        {
            sum[i]=sum[i+1];
            if((S[i-1]-'0')%mod==0)
                sum[i]++;
        }//记录后缀中有多少个数是合法末位
        for(int i=n;i>0;i--)
            q[i]=sum[i]+q[i+1];//记录后缀的后缀和,也就是后缀中有多少合法的区间
        for(int i=1;i<=m;i++)
            printf("%lld\n",q[k[i].l]-q[k[i].r]-(k[i].r-k[i].l)*sum[k[i].r]);//计算答案:区间的后缀和相减,再减去区间之外的末位对区间的贡献
        return 0;
    }
    sort(k+1,k+m+1,cmp);
    for(int i=1;i<=9;i++)
    {
        ano[i][1]=i%mod;
        for(int j=2;j<=n;j++)
            ano[i][j]=1ll*ano[i][j-1]*10%mod;//计算每个数作为第 j 位时在 %P 下的值
    }
    for(int i=n;i>=1;i--)
    {
        ls[i].rest=(ls[i+1].rest+ano[S[i-1]-'0'][n-i+1])%mod;//记录每个 r[i] 的 %P 之后的值
        ls[i].d=i;
    }
    sort(ls+1,ls+n+1,cmp2);
    for(int i=1;i<=n;i++)
    {
        if(ls[i].rest>ls[i-1].rest)cnt++;
        rest[ls[i].d]=cnt;
    }//将余数离散化才能存
    L=k[1].l,R=k[1].l-1;
    for(int i=1;i<=m;i++)
    {
        while(L<k[i].l)Del(L++);
        while(L>k[i].l)Add(--L);
        while(R<k[i].r)Add(++R);
        while(R>k[i].r)Del(R--);
        answer[k[i].d]=ans;
    }
    for(int i=1;i<=m;i++)
        printf("%lld\n",answer[i]);
}
posted @ 2019-04-02 20:04 洛水·锦依卫 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值