BZOJ 2820

题目

$T$个询问,每个询问求有多少对$(x,y)$使得$gcd(x,y)$是质数,其中$1 \leq x \leq N \quad 1 \leq y \leq M$。

数据范围

$T \leq 10^4 \quad N,M \leq 10^7$

做法

所求为:
$$
\sum{p是质数}\sum{x=1}^N \sum_{y=1}^M [gcd(x,y)==p]
$$

显然可转化为:
$$
\sum{p是质数} \sum{x=1}^{N’} \sum_{y=1}^{M’}[gcd(x,y)==1] \quad \quad N’=\frac{N}{p} \quad M’=\frac{M}{P}
$$

对内部2个合式进行莫比乌斯反演。

定义:
$$
g(i):=gcd(x,y)等于i的(x,y)的对数
$$

$$
f(i):=i|gcd(x,y)的(x,y)的对数
$$
显然,有:
$$
f(i)=\lfloor \frac{N’}{i} \rfloor \times \lfloor \frac{M’}{i} \rfloor=\lfloor \frac{N}{p\times i} \rfloor \times \lfloor \frac{M}{p\times i} \rfloor
$$

反演一蛤:
$$
f(i)=\sum{i|d}g(d) \iff g(i)=\sum{i|d} \mu(\frac{d}{i}) f(d)
$$

所求为:
$$
\sum{p是质数}g(1)=\sum{p是质数}\sum{1|d} \mu(d) f(d)=\sum{p是质数}\sum_{1|d} \mu(d) \times\lfloor \frac{N}{p\times d} \rfloor \times \lfloor \frac{M}{p\times d} \rfloor
$$
若对上式直接枚举p来计算,则一次询问的复杂度是$O(质数p的个数 \times \frac{N}{p}的约数个数)$,复杂度太大。

考虑继续化简。令$p\times d = T$,改变求和次序,则上式可化简为:
$$
\sum{T=1}^{min(N,M)}\lfloor \frac{N}T{} \rfloor \times \lfloor \frac{M}{T} \rfloor \times \sum{p是质数且p|T}\mu(\frac{T}{p})
$$
对于$h(T)=\sum_{p是质数且p|T}\mu(\frac{T}{p})$,可以通过埃氏筛法,近乎线性地求出:对于每个素数,把它地对和式的贡献更新到对应的和式中即可。

求出$h(T)$的前缀和之后,便可以分段来加速整个求和过程了。

这样,一次询问的复杂度为$O(N的约数个数 + M的约数个数)$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <bits/stdc++.h>
using namespace std;
const int MAX_NUM = 1e7 + 5;
typedef long long ll;
int N, M;
bool isPrime[MAX_NUM];
int mu[MAX_NUM];
int f[MAX_NUM];
void getMu(int n)
{
for (int i = 1; i <= n; ++i) {
mu[i] = 1;
isPrime[i] = true;
}
for (int i = 2; i <= n; ++i) {
if (isPrime[i]) {
mu[i] = -1;
for (int j = 2 * i; j <= n; j += i) {
mu[j] *= -1;
if ((j / i) % i == 0) mu[j] = 0;
isPrime[j] = false;
}
}
}
for (int i = 2; i <= n; ++i) {
if (isPrime[i]) {
for (int j = i; j <= n; j += i) {
f[j] += mu[j / i];
}
}
}
}
ll calc(int n, int m)
{
if (n > m) swap(n, m);
ll sum = 0;
int last;
for (int i = 1; i <= n; i = last + 1) {
last = min(n / (n / i), m / (m / i));
sum += (ll)(n / i) * (m / i) * (f[last] - f[i - 1]);
}
return sum;
}
int main()
{
getMu(MAX_NUM - 1);
for (int i = 1; i < MAX_NUM; ++i) f[i] += f[i - 1];
int T; cin >> T;
while (T--) {
scanf("%d%d", &N, &M);
printf("%lld\n", calc(N, M));
}
return 0;
}

总结

对于和式的化简,重点在于对求和过程的理解。可以通过改变求和的顺序来化简和式。