「题目描述」
设 $d(x)$ 为 $x$ 的约数个数,给定 $n, m$ ,求: $$ \sum_{i = 1}^n \sum_{j = 1}^m d(ij) $$ $n, m \leq 50000$ ,$T$ 组询问,$T \leq 50000$ 。
「题目链接」
BZOJ 3994 约数个数和 「SDOI 2015」
「解题思路」
首先我们有这样的一个结论:
$$ d(ij) = \sum_{r \mid i} \sum_{s \mid j} [(r, s) = 1] $$
分解,设 $ij = \prod p_k^{c_k}$ ,则 $d(ij) = \prod (c_k + 1)$ ,这是约数个数的一般公式。
下面说明等式右边能给出同样的结果。
设 $i = \prod p_k^{a_k}$ ,$j = \prod p_k^{b_k}$ ,应有 $a_k + b_k = c_k$ 。
那么我们考虑 $r = \prod p_k^{x_k}, s = \prod p_k^{y_k}$ ,欲使 $(r, s) = 1$ ,对所有的 $k$ ,都应满足 $x_k = 0$ 或 $y_k = 0$。
然后考虑对于每个 $k$ ,满足该条件的 $x_k, y_k$ 组合方式有多少。
当 $x_k = 0, y_k = 0$ 时,仅有 1 种 。
当 $x_k \not = 0, y_k = 0$ 时,$x_k$ 可以取 $1 \dots a_k$ ,共 $a_k$ 种。
当 $x_k = 0, y_k \not = 0$ 时,$y_k$ 可以取 $1 \dots b_k$ ,共 $b_k$ 种。
所以每个 $k$ 有 $a_k + b_k + 1 = c_k + 1$ 种,再根据乘法原理,给出 $d(i, j) = \prod (c_k + 1)$ ,证毕。
得到这个结论后,我们从其出发,推导一番: $$ \begin{align*} d(i, j) &= \sum_{r \mid i} \sum_{s \mid j} [(r, s) = 1] \newline &= \sum_{r \mid i} \sum_{s \mid j} \sum_{x \mid (r, s)} \mu(x) \newline &= \sum_{x} \mu(x) \sum_{r \mid i} \sum_{s \mid j} [x \mid (r, s)] \newline &= \sum_{x} \mu(x) \sum_{r \mid i} \sum_{s \mid j} [x \mid r \land x \mid s] \newline &= \sum_{x} \mu(x) \sum_{r \mid i} \sum_{s \mid j} [x \mid r][x \mid s] \newline &= \sum_{x} \mu(x) \sum_{r \mid i} [x \mid r] \sum_{s \mid j} [x \mid s] \end{align*} $$ 我们将指出: $$ \sum_{r \mid i} [x \mid r] = d(\frac i x) [x \mid i] $$ 这是求满足 $x \mid r, r \mid i$ 的 $r$ 的个数,为了保证 $x \mid r$ ,干脆设 $r = tx$ ,那么就是求使 $tx \mid i$ 的 $t$ 的个数,首先必须要有 $x \mid i$ ,然后就是使 $t \mid \frac i x$ 的 $t$ 的个数,也就是 $d(\frac i x)$ 咯。
综上,我们有: $$ d(ij) = \sum_{x} \mu(x) d(\frac i x)[x \mid i] d(\frac j x) [x \mid j] $$ 考虑答案: $$ \begin{align*} \sum_{i = 1}^n \sum_{j = 1}^m d(ij) &= \sum_{i = 1}^n \sum_{j = 1}^m \sum_{x} \mu(x) d(\frac i x) [x \mid i] d(\frac j x) [x \mid j] \newline &= \sum_{x} \mu(x) \sum_{i = 1}^n d(\frac i x) [x \mid i] \sum_{j = 1}^m d(\frac j x) [x \mid j] \newline &= \sum_{x} \mu(x) \sum_{i' = 1}^{\left \lfloor \frac n x \right \rfloor} d(i') \sum_{j' = 1}^{\left \lfloor \frac m x \right \rfloor} d(j') \end{align*} $$ $d$ 是积性函数,可以线性筛 $O(n)$ 预处理。
然后成块统计答案,就可以做到单次询问 $O(\sqrt n)$ 了。
「AC 代码」
#include <cstdio>
#include <algorithm>
typedef long long int64;
const int MAX_N = 50000;
bool isNotPrime[MAX_N + 1];
int min[MAX_N + 1], cnt[MAX_N + 1];
int primes[MAX_N], m;
int64 mu[MAX_N + 1], d[MAX_N + 1];
inline void sieve(int n = MAX_N){
isNotPrime[1] = true, min[1] = 0, cnt[1] = 1, mu[1] = 1, d[1] = 1;
for(int i = 2; i <= n; i++){
if(!isNotPrime[i]){
primes[m++] = i;
min[i] = i, cnt[i] = 1;
mu[i] = -1, d[i] = 2;
}
for(int j = 0, p, x; j < m; j++){
if((x = i * (p = primes[j])) > n) break;
isNotPrime[x] = true;
if(i % p == 0) min[x] = min[i] * p, cnt[x] = cnt[i] + 1; else min[x] = p, cnt[x] = 1;
if(x == min[x]){
mu[x] = 0;
d[x] = cnt[x] + 1;
} else{
mu[x] = mu[ min[x] ] * mu[ x / min[x] ];
d[x] = d[ min[x] ] * d[ x / min[x] ];
}
if(i % p == 0) break;
}
}
for(int i = 1; i <= n; i++) mu[i] += mu[i - 1], d[i] += d[i - 1];
}
inline int64 solve(int n, int m){
if(n > m) std::swap(n, m);
int64 ans = 0;
for(int l = 1, r; l <= n; l = r + 1){
r = std::min(n / (n / l), m / (m / l));
ans += (mu[r] - mu[l - 1]) * d[n / l] * d[m / l];
}
return ans;
}
int main(){
sieve();
int T;
scanf("%d", &T);
for(int i = 0, n, m; i < T; i++){
scanf("%d%d", &n, &m);
printf("%lld\n", solve(n, m));
}
return 0;
}
就是这样咯~