主要思路
我们首先可以搞到一个统计答案的式子:
\[ \sum_{k \in prime} \sum_{i=1}^{n} \sum_{j=1}^{m} [gcd(i, j) = k] \\ \sum_{k \in prime} \sum_{i=1}^{\lfloor \frac{n}{k} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{k} \rfloor} [gcd(i,j)=1] \\ \sum_{k \in prime} \sum_{i=1}^{\lfloor \frac{n}{k} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{k} \rfloor} \sum_{d|gcd(i,j)} \mu(d) \\ \sum_{k=1, k \in prime}^{n} \sum_{d=1}^{\lfloor \frac{n}{k} \rfloor} \mu(d) \lfloor \frac{n}{kd} \rfloor \lfloor \frac{m}{kd} \rfloor \\ \text{设}T=kd \\ \sum_{k=1, k \in prime}^{n} \sum_{d=1}^{\lfloor \frac{n}{k} \rfloor} \mu(d) \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \\ \sum_{T=1}^{n} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \sum_{k|T} \mu(\frac{T}{k}) \]
在这个最终的式子里,我们可以注意到可以对\(\lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor\)进行整数分块,将时间降到根号级别(前提是处理前缀和)。
啊呀,看代码吧。
代码
// P2257.cpp #include <bits/stdc++.h> #define ll long long using namespace std; const int MAX_N = 10001000; int T, miu[MAX_N], prime[MAX_N], cnt, prefix[MAX_N], sum[MAX_N]; bool flag[MAX_N]; void sieve() { miu[1] = 1; for (int i = 2; i < MAX_N; i++) { if (!flag[i]) prime[++cnt] = i, miu[i] = -1; for (int j = 1; j <= cnt && i * prime[j] < MAX_N; j++) { flag[i * prime[j]] = true; if (i % prime[j] == 0) break; miu[i * prime[j]] = -miu[i]; } } for (int i = 1; i <= cnt; i++) for (int j = 1; prime[i] * j < MAX_N; j++) prefix[j * prime[i]] += miu[j]; for (int i = 1; i < MAX_N; i++) sum[i] = sum[i - 1] + prefix[i]; } ll solve(int a, int b) { ll ans = 0; if (a > b) swap(a, b); for (int l = 1, r = 0; l <= a; l = r + 1) { r = min(a / (a / l), b / (b / l)); ans += (ll)(sum[r] - sum[l - 1]) * (ll)(a / l) * (ll)(b / l); } return ans; } int main() { sieve(); scanf("%d", &T); while (T--) { int n, m; scanf("%d%d", &n, &m); if (n > m) swap(n, m); printf("%lld\n", solve(n, m)); } return 0; }