前置芝士
\(\epsilon(n) = [n=1]\)
\(1 * \mu = \epsilon\)
解
(为了避免变量重名我把题目中的 \(d\) 换成 \(D\) 了 -_- ||)
设 \(n = \lfloor \frac{a}D \rfloor\) , \(m = \lfloor \frac{b}D \rfloor\) ,
然后上套路
\[\sum_{i=1}^a \sum_{j=1}^b [gcd(i,j)=D] \]
\[=\sum_{i=1}^n \sum_{j=1}^m [gcd(i,j)=1] \]
\[=\sum_{i=1}^n \sum_{j=1}^m \epsilon(gcd(i,j)) \]
\[=\sum_{i=1}^n \sum_{j=1}^m \sum_{d|gcd(i,j)} \mu(d) \]
由于 \(d|gcd(i,j)\), 所以 \(d\) 是 \(i\) 和 \(j\) 的公约数。
式子是计每个 \(i\) 和 \(j\) 公约数的 \(\mu\)和 之和, 每个 \(d\) 都作为公约数被计了 \(\lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor\) 次, 所以上式变为
\[\sum_{d=1}^{min(n,m)} \mu(d) \lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor \]
\(a\) 、 \(b\) 的范围很小, 可以 \(O(n)\) 预处理\(\mu\), 再用整除分块计算这个式子, 单次计算的复杂度是 \(O(\sqrt n)\) 的, 由于有 \(n\) 次询问, 可以 \(O(n \sqrt n)\) 的复杂度在 \(2s\) 时限内莽过去。
规范的反演推理会补。
Luogu数据AC代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 50005;
#define int long long
int m, prime[maxn], v[maxn], mu[maxn];
void euler(int n) {
mu[1] = 1;
for(int i=2; i<=n; ++i) {
if(!v[i]) {
v[prime[++m] = i] = i;
mu[i] = -1;
}
for(int j=1; j<=m; ++j) {
if(prime[j] > n/i || prime[j] > v[i]) break;
v[prime[j] * i] = prime[j];
mu[prime[j] * i] = mu[i] * (i%prime[j] ? -1 : 0);
}
}
}
long long sol(int n, int m) {
int len = min(n, m);
long long res = 0ll;
for(int i=1,j; i<=len; i=j+1) {
j = min(n/(n/i), m/(m/i));
j = min(j, len);
res += (mu[j]-mu[i-1]) * (n/i) * (m/i);
}
return res;
}
signed main()
{
euler(50000);
for(int i=1; i<=50000; ++i) mu[i] += mu[i-1];
int n; cin >> n; while(n--) {
int a, b, d;
scanf("%lld%lld%lld", &a, &b, &d);
cout << sol(a/d, b/d) << '\n';
}
return 0;
}