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) 行,每行两个整数 ( ext{fr}, ext{to}),表示对 (S) 的子串 (S[ ext{fr} ldots ext {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
第一个询问问的是整个串,满足条件的子串分别有:121121
、2112
、11
、121
、121
。
Range
对于所有的数据,(N,M leq 100000),(P) 为素数。
Algorithm
莫队
Mentality
嗯 (......) 比较送分的题目。
支持 (nlog) 的数据范围,可离线的区间询问,且问的内容一看就可以莫队 = = 。
我们发现,设 (r[i]) 为 ([i,n]) 组成的数,那么有:
我们把分母移到左边去,那么便有:
那么,当 (r[i]) 与 (r[j+1]) 对 (P) 取模的余数相同,那等式就必定等于 (0) ,则有:
那么当 (10^{n-j} mod P eq 0) 时,因为最后的结果为 (0) ,所以必有 (num(i,j) mod P=0) 。
也就是说,当 (P eq 2,5) 时,(num(i,j)) 为 (P) 的倍数当且仅当 (r[i]) 和 (r[j+1]) 关于模数 (P) 同余。
那么我们的问题就变成了一个区间内有多少属性相同的点对了,这个就很莫队了。
至于 (P=2,5) 的时候,特判处理即可。我们可以直接通过判断一个数的末位来判断这个数是否为 (P) 的倍数,我们可以直接记前缀和哇。
Code
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
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
",
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
", answer[i]);
}