[题解] SDOI2015 约数个数和

题目描述

设 $d(x)$ 为 $x$ 的余数个数,给定 $N$ 和 $M$,求 $\sum_{i=1}^N \sum_{j=1}^M d(ij)$

输入格式

输入文件包含多组测试数据。第一行,一个整数 $T$,表示测试数据的组数。接下来的 $T$ 行,每行两个整数 $N, M$。

输出格式

$T​$ 行,每行一个整数,表示你所求的答案。

样例输入

2
7 4
5 6

样例输出

110
121

数据范围及约束

$T, N, M\leq 50\ 000$

解题思路

首先我们要知道 $d(ij) = \sum_{p \mid i} \sum_{q \mid j} [\gcd([p, q]) = 1] $,这个在之后给出了证明
$$
\begin{aligned}
& \sum_{i=1}^n \sum_{j=1}^m d(ij) \\
= & \sum_{i=1}^n \sum_{j=1}^m \sum_{p \mid i} \sum_{q \mid j} [\gcd(p, q) = 1] \\
= & \sum_{p=1}^n \sum_{q=1}^m \sum_{i=1}^n \sum_{j=1}^m [p \mid i] [q \mid j] [\gcd(p, q) = 1] \\
= & \sum_{p=1}^n \sum_{q=1}^m \lfloor \frac{n}{p} \rfloor \lfloor \frac{m}{q} \rfloor [gcd(p, q) = 1] \\
= & \sum_{p=1}^n \sum_{q=1}^m \lfloor \frac{n}{p} \rfloor \lfloor \frac{m}{q} \rfloor \sum_{k \mid p} \sum_{k \mid q} \mu(k) \\
= & \sum_{k=1}^{\min(i, j)} \mu(k) \sum_{p=1}^n \sum_{q=1}^m \lfloor \frac{n}{p} \rfloor \lfloor \frac{m}{q} \rfloor[k \mid p] [k \mid q]\\
= & \sum_{k=1}^{\min(i, j)} \mu(k) \sum_{p=1}^{n/k} \sum_{q=1}^{m/k} \lfloor \frac{n}{pk} \rfloor \lfloor \frac{m}{qk} \rfloor
\end{aligned}
$$
可以先预处理出 $\sum_{i=1}^n \lfloor \frac{n}{i} \rfloor$ 在 $1, 2, \cdots, 50 \ 000$ 处的答案,然后可以 $O(1)$ 询问后半部分的答案,再套用整除分块,在 $O(\sqrt{n})$ 的时间内做出单组询问

证明
$$
d(ij) = \sum_{p \mid i} \sum_{q \mid j} [gcd(p, q) = 1]
$$

对于一个 $i​$ 和 $j​$ 中只含 $k​$ 这个质因子,假设 $i​$ 中含有 $a​$ 个,$j​$ 中含有 $b​$ 个,那么 $i \times j​$ 就有 $a + b + 1​$ 个约数

而 $\sum_{p \mid i} \sum_{q \mid j} [gcd(p, q) = 1] ​$ 中有

  • $p​$ 取 $k^0​$,$q​$ 取 $k^0, k^1, k^2, \cdots, k^b​$ 共 $b+1​$ 种
  • $p​$ 取 $k^0, k^1, k^2, \cdots, k^a​$ ,$q​$ 取 $k^0​$ 共 $a+1​$ 种
  • 上面两种情况中重复计算了 $p​$ 取 $k^0​$ 且 ​$q​$ 取 ​$k^0​$ 的情况,减去 ​$1​$ 种

因此对于只含有单一质因子的情况,这个式子是成立的

假设对于含有前 $n$ 个 $(n \geq 1)$ 质因子的情况成立,即 $i = \prod_{s=1}^n k_s^{a_s}$,$j = \prod_{s=1}^n k_s^{b_s}$ 已经成立,共有 $\prod_{s=1}^{n} (a_s + b_s) + 1$ 个约数,记为 $S$

考虑假如第 $n+1​$ 个质因子,之前已经有 $S​$ 个两两互质的二元组 $<x_i, y_i>​$,第 $n+1​$ 个因数 $p​$ 中有 $a​$ 个,$q​$ 中有 $b​$ 个

  • $p$ 取 $x_i \times k^0$ ,$q$ 取 $y_i \times k^0, y_i \times k^1, \cdots, y_i \times k^b$ 共 $S \times (b+1)$ 种
  • $p​$ 取 $x_i \times k^0, x_i \times k^1, \cdots, x_i \times k^a​$ ,$q​$ 取 $y_i \times k^0​$ 共 $S \times (a+1)​$ 种
  • 上面两种情况重复计算了 $p​$ 取 $x_i \times k^0​$ 且 $q​$ 取 $y_i \times k^0​$ 的情况,共 $S​$ 种

总共有 $S \times (a+b+1)​$ 种,由归纳法证明成立

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;
const int MAXN = 50000 + 10;
const int SIZ = 50000;

int pri[MAXN], cnt;
long long sum[MAXN], num[MAXN], mu[MAXN];
bool isNotPrime[MAXN];

inline void init() {
    mu[1] = 1;

    for (register int i=2; i<=SIZ; i++) {
        if (!isNotPrime[i]) {
            mu[i] = -1;
            pri[++cnt] = i;
        }

        for (register int j=1; j<=cnt; j++) {
            int m = i * pri[j];
            if (m > SIZ) break;
            isNotPrime[m] = true;

            if (i % pri[j] == 0) {
                mu[m] = 0;
                break;
            } else {
                mu[m] = -mu[i];
            }
        }
    }

    for (register int i=1; i<=SIZ; i++) {
        sum[i] = sum[i-1] + mu[i];
    }

    for (register int i=1; i<=SIZ; i++) {
        int r = 0;
        long long res = 0;

        for (register int l = 1; l <= i; l = r + 1) {
            r = i / (i / l);
            res += (r - l + 1) * (i / l);
        }

        num[i] = res;
    }
}

inline long long solve(int n, int m) {
    int r = 0;
    long long res = 0;

    for (register int l=1; l <= min(n, m); l = r + 1){
        r = min(n / (n / l), m / (m / l));
        res += (sum[r] - sum[l-1]) * num[n / l] * num[m / l];
    }

    return res;
}

int T, n, m;

int main(){
    init();
    scanf("%d", &T);

    for (register int i=1; i<=T; i++) {
        scanf("%d%d", &n, &m);
        printf("%lld\n", solve(n, m));
    }

    return 0;
}