【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 <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\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 洛水·锦依卫 阅读(...) 评论(...) 编辑 收藏