BZOJ 2301

题目

给出$T$个询问$(a,b,c,d,K)$,每次求有多少个数对$(x,y)$的gcd等于$K$,其中$a \leq x \leq b \quad c \leq y \leq d$。

数据范围

$0 \leq T, a, b, c, d, K \leq 50000$

做法

显然,可用容斥定理将问题转化为:有多少对$(x,y)$的gcd等于$K$,其中$1 \leq x \leq n \quad 1 \leq y \leq m$。

问题可继续转化为:有多少对$(x,y)$的gcd等于1,其中$1 \leq x \leq n’ = \lfloor \frac{n}{K} \rfloor \quad 1 \leq y \leq m’ = \lfloor \frac{m}{K} \rfloor$

考虑莫比乌斯反演。
$$
g(i):=gcd(x,y)为i的(x,y)的对数
$$

$$
f(i):=gcd(x,y)为i的倍数的(x,y)的对数
$$

显然有:
$$
f(i)=\lfloor \frac{n’}{i} \rfloor \times \lfloor \frac{m’}{i} \rfloor
$$

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

所求即为:
$$
g(1)=\sum{1|d}\mu(d)\times f(d)=\sum{1|d}\mu(d)\times \lfloor \frac{n’}{d} \rfloor \times \lfloor \frac{m’}{d} \rfloor
$$
到这里,我们从$1$到$min(n’,m’)$枚举$d$,即可$O(N)$地处理出每个询问。但是时间复杂度还是太高。

进行优化。注意到:$\lfloor \frac{n’}{d} \rfloor$只有$2\times num(n’)$种不同的值,其中$num(n)表示n的约数个数$。也就是说,对于不同的$d$,$\lfloor \frac{n’}{d} \rfloor$的值是一样的。所以,对于$ \lfloor \frac{n’}{d} \rfloor \times \lfloor \frac{m’}{d} \rfloor$的值相同的一系列$d$,通过预处理出$\mu(i)$的前缀和,可以把这个相同的值提出来,通过一步乘法来代替原来的多步加法,以加速计算。具体实现见代码。

$ \lfloor \frac{n’}{d} \rfloor \times \lfloor \frac{m’}{d} \rfloor$最多有$num(n’) + num(m’)$个不同的值。所以一次询问的复杂度为$O(num(n’) + num(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
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_NUM = 5e4 + 5;
int A, B, C, D, K;
bool isPrime[MAX_NUM];
int mu[MAX_NUM];
int smu[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 = i * 2; j <= n; j += i) {
mu[j] *= -1;
if ((j / i) % i == 0) mu[j] = 0;
isPrime[j] = false;
}
}
}
}
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) * (smu[last] - smu[i - 1]);
}
return sum;
}
int main()
{
getMu(MAX_NUM - 1);
for (int i = 1; i < MAX_NUM; ++i) {
smu[i] = smu[i - 1] + mu[i];
}
int T; cin >> T;
while (T--) {
scanf("%d%d%d%d%d", &A, &B, &C, &D, &K);
--A; --C;
A /= K; B /= K; C /= K; D /= K;
printf("%lld\n", calc(B, D) - calc(A, D) - calc(B, C) + calc(A, C));
}
return 0;
}