[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个询问的答案。

 

输入输出样例

输入样例#1:
11
121121
3
1 6
1 5
1 4
输出样例#1:
5
3
2
//第一个询问问的是整个串,满足条件的子串分别有:121121,2112,11,121,121。

说明

2016.4.19新加数据一组

把每一个后缀i~n的值%p算出来lst[i]

一个子串整除p满足:lst[i]=lst[j+1] (j>=i)

所以用莫队处理询问,每一次移动都是很好计算的

r向右走一步,相当于加上当前与lst[++r]相同的值ss[lst[++r]],在ss[lst[++r]]++

r向左走一步,相当于减去当前与lst[r]相同的值ss[lst[r]]--,再把r--

l相似

注意上面的条件和下面的莫队处理的都是lst[i]=lst[j+1],所以要把询问的右端点+1

还有要注意的,当p=2或p=5时要特判(不这样对不了)

这两个素数比较特殊

只要i能整除p,那么所有j~i都能整除p

用ss[]表示 前缀有多少个可以被p整除的子串 ls[]表示有多少个可以被P整除的数 求区间多少个子串的时候用ss[r]-ss[l-1]-(l-1中整除p对区间l~r的贡献)

=ss[r]-ss[l-1]-(l-1)*(ls[r]-ls[l-1])

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<map>
 7 using namespace std;
 8 typedef long long ll;
 9 struct Ask
10 {
11     ll id,l,r;
12 }q[100005];
13 ll sqn,m,n,lst[100005],ls[100005],ans[100005];
14 ll ss[100005];
15 map<ll,ll>Map;
16 char s[100005];
17 bool cmp(Ask a,Ask b)
18 {
19     if (a.l/sqn==b.l/sqn)
20     return a.r<b.r;
21     return a.l/sqn<b.l/sqn;
22 }
23 ll p;
24 int main()
25 {ll bt,i,l,r;
26     cin>>p;
27     cin>>s+1;
28     cin>>m;
29     n=strlen(s+1);
30     sqn=sqrt(n);
31     bt=1;
32     if (p!=2&&p!=5)
33     {
34     for (i=n;i>=1;i--)
35     {
36         bt=bt*10%p;
37         lst[i]=(lst[i+1]+(s[i]-'0')*bt)%p;
38         ls[i]=lst[i];
39     }
40     sort(ls+1,ls+n+1);
41     for (i=1;i<=n+1;i++)
42     Map[ls[i]]=i;
43     for (i=1;i<=n+1;i++)
44     lst[i]=Map[lst[i]];
45     for (i=1;i<=m;i++)
46     {
47      scanf("%lld%lld",&q[i].l,&q[i].r);
48      q[i].r++;
49      q[i].id=i;
50     }
51     sort(q+1,q+m+1,cmp);
52     int l=1,r=0;
53     long long cnt=0;
54     for (i=1;i<=m;i++)
55     {
56       while (r<q[i].r) cnt+=ss[lst[++r]]++;
57       while (r>q[i].r) cnt-=--ss[lst[r--]];
58       while (l>q[i].l) cnt+=ss[lst[--l]]++;
59       while (l<q[i].l) cnt-=--ss[lst[l++]];
60       ans[q[i].id]=cnt;
61     }
62     for (i=1;i<=m;i++)
63     printf("%lld\n",ans[i]);
64     }
65     else 
66     {
67         for (i=1;i<=n;i++)
68         if (!((s[i]-'0')%p))
69         ss[i]=ss[i-1]+1,ls[i]=ls[i-1]+i;
70         else ss[i]=ss[i-1],ls[i]=ls[i-1];
71         for (i=1;i<=m;i++)
72         {
73             scanf("%lld%lld",&l,&r);
74             printf("%lld\n",ls[r]-ls[l-1]-(ss[r]-ss[l-1])*(l-1));
75         }
76     }    
77 }

 

转载于:https://www.cnblogs.com/Y-E-T-I/p/7670162.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值