GCD素数数对计算
Erstellt am: 18. Januar 2025
Erstellt am: 18. Januar 2025
Formulating the approach
I’m working through the problem of counting pairs (x, y) where the GCD is a prime number. The solution needs to be efficient and handle up to 1e7 elements in C++.
Refining the approach
I'm working on counting pairs (x, y) where the GCD is a prime by examining x = p * a and y = p * b, ensuring gcd(a, b) = 1.
Piecing together
I’m noting that the formula for f(M) counts ordered pairs of (a, b) with gcd(a, b) = 1 up to M, leveraging the symmetry of numbers and known results.
Orchestrating calculations
Considering an efficient O(N log log N) method using prefix sums and the Mobius function, or applying a known identity for counting coprime pairs to streamline calculations.
Revealing patterns
It's interesting to see that the number of pairs (a, b) with gcd(a, b) = d can be expressed as the sum of f(M/d) for divisors of d.
Piecing together
I’m using the formula f(M) = sum_{k=1 to M} mu(k) * floor(M/k)^2, leveraging the Möbius function and inclusion-exclusion principle to efficiently count coprime pairs up to N.
Calculating pairs
I’m summing f(N/p) for each prime up to N. This involves computing coprime pairs up to N/p, which should be more manageable.
Refining the strategy
I’m now counting pairs (a, b) with gcd(a, b) = 1 and 1 ≤ a, b ≤ floor(N/p) for each prime p. The final task is to efficiently compute the sum for N up to 1e7.
Assessing distinct values
I’m analyzing the number of distinct values of floor(N/p) for each prime p, noting it could be up to O(N), which is quite large.
Leveraging computational methods
I’m examining efficient techniques to calculate floor(N/d) for each prime p, considering a sieve of Eratosthenes and the Möbius function, and preferring prefix sums for optimization.
Summing efficiently
I’m thinking through summing mu(d) * floor(M/d)^2 by splitting the domain into intervals where floor(M/d) remains constant, utilizing prefix sums of mu(d) for efficiency.
Calculating efficiently
I’m outlining the steps: determine distinct values of floor(M/d), calculate f(M) for each value using prefix sums of mu(d), and handle up to 664k distinct queries efficiently.
Adjusting my focus
I’m thinking that handling 2.1 billion operations might be tough. By noting that the number of distinct values of floor(N/d) for d up to N is about 2 sqrt(N), it’s clearer how to approach it.
Reducing the scope
By narrowing down operations to around 20 million, it’s possible to sieve primes up to N, find distinct floor(N/p) values for primes, and sum results for efficient calculation.
Calculating distinct values
I’m working on compressing distinct values of floor(N/d), computing f(value) in O(sqrt(value)) using a prefix sum of the Möbius function, and efficiently summing partial sums.
Mapping out the process
I’m detailing an algorithm: precompute the Möbius function, create prefix sums, and efficiently calculate f(M) for each distinct M using partial sums of the Möbius function.
Generating primes
I’m piecing together primes up to N using a sieve, computing M values, and efficiently calculating f(M) for each distinct M with prefix sums of the Möbius function.
Using 64-bit integers
I’m preparing for a large result by considering 64-bit integers to manage counts and ensure precision for pairs (x, y) where gcd(x, y) is a prime.
Mapping pairs
I’m identifying pairs (x, y) with gcd equal to prime numbers. In the example with N=4, four pairs meet this criterion.
Setting up the sieve
I’m initializing and updating the mu array using the sieve method, careful to set values to 0 where necessary and consider time constraints for efficient computation.
Mapping out the sieve
I’m detailing the sieve process to compute the mu array, identifying primes, and using prefix sums for efficient computation, while managing memory constraints for large arrays.
Managing memory efficiently
I'm mapping out ways to handle large arrays and calculations, like using 32-bit integers for prefix sums of mu and 64-bit for final results, to stay within memory limits.
Focusing on coding
I’m planning the code to handle large inputs, aiming to efficiently compute gcd pairs, and considering how to manage computational capacity and token limits to ensure an effective solution.
Thought about GCD and prime pairs,持续 36 秒
思路概要(请先阅读本节以理解整体算法,再看代码)
题目要求:
给定 ,统计所有满足 且 为质数的有序对 的数量。记该数量为 。
令质数 。则可写
并且
从而对于固定的质数 ,能与之匹配的 对的数量即是
其中
因此题目所求总数为
记
这是在区间 上互质对 的个数。
一个经典结果是:
其中 是米宾函数(Möbius 函数),定义如下:
因此我们只要能快速地:
就能解决问题。
直接按照定义做需要 时间,若对每个质数 (可能多达 个)都要做一次,会非常大,无法在 最大到 时完成。
但我们可以用分块(或称「分段」)技术,在约 时间内计算该和:
将 的值记为 。当 固定时, 会落在一个连续区间内。具体做法:
i
从 1 递增到 ,但不是逐一 +1,而是跳到 的下一个,每次区间内 值相同,可将该区间 的 之和用前缀和一次性求出,再乘以相同的 。在最终答案
中,不同质数 可能得到相同的 。我们只需去重这些值,然后对每个去重值 计算一次 ,放到哈希表或数组里,之后在循环中直接取值相加即可。
当 时,质数最多约 66 万多个,但 的不同取值个数典型情况下约为 级别(更严格的估计与分块技巧类似),大约几千到上万级,计算总量仍在合理范围内(配合 C++ 优化后可以通过)。
vals
。vals
中每个元素 m
,用前缀和 + 分块技术在 时间内计算
存入字典或数组 fval[m]
。m = floor(N/p)
,把 fval[m]
加起来即可。最终输出此和。
题目示例 :
( 注:最后我们要数的是 gcd=质数 的对,这个数经过推导就是 。与直接列举得到 4 对完全一致。)
以下是一份相对完整的参考实现,使用了 C++ 常见 OI 写法,头文件 <bits/stdc++.h>
,命名空间 std
,并做了相应的注释。算法核心就在计算 函数时使用的「分块 + 前缀和」技巧。
重要提醒:
- 若实际比赛/测评环境对内存非常苛刻(如只给 256MB),要注意 1e7 大小数组是否可行,可以考虑进行更细的空间优化或使用分段式计算。
- 若时间紧张,也可能需进一步优化(如更高效的 IO, 更精简的循环等)。
#include <bits/stdc++.h> using namespace std; static const int MAXN = 10000000; // 题目给出的 N 上限 // 全局数组,视情况可改为静态/动态分配。 // mu[i] 存储 Möbius 函数 // prefixMu[i] 存储 mu 的前缀和: prefixMu[i] = mu[1] + ... + mu[i] static int mu[MAXN+1]; static int prefixMu[MAXN+1]; // 存放最小质因子或用于标记质数的数组,可用来线性筛。 static int spf[MAXN+1]; // 存储所有质数 static vector<int> primes; // 线性筛,计算 mu[] 和 primes。 void computeMuAndPrimes(int n) { // mu(1) = 1 mu[1] = 1; for(int i=2; i<=n; i++){ if(spf[i] == 0) { // i 是质数 spf[i] = i; mu[i] = -1; // 质数的 mu 值为 -1 primes.push_back(i); } // 给当前的质数表做“筛”: for(int &p: primes){ if(p > spf[i] || 1LL * p * i > n) break; spf[p*i] = p; if(i % p == 0){ // 若 i 已经含有 p, 那么 p^2| (p*i), 则 mu=0 mu[p*i] = 0; break; } else { mu[p*i] = - mu[i]; } } } // 计算前缀和 prefixMu for(int i=1; i<=n; i++){ prefixMu[i] = prefixMu[i-1] + mu[i]; } } // 利用分块 + prefixMu[], 计算 f(M) = sum_{d=1..M} mu[d] * (floor(M/d))^2 // 时间复杂度约 O(sqrt(M))。 long long calcF(long long M) { // 特判 if(M <= 0) return 0; // 分块求和 long long ans = 0; for(long long i=1; i<=M; ){ long long v = M / i; // floor(M/i) long long r = M / v; // 使得 floor(M/d) = v 对应 d 在 [i, r] // 区间 [i..r] 的 mu(d) 之和 long long sumMu = prefixMu[r] - prefixMu[i-1]; ans += sumMu * (long long)v * v; i = r + 1; } return ans; } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int N; cin >> N; // 预处理 mu 和 primes computeMuAndPrimes(N); // 收集所有 floor(N/p) (p 为质数),并去重 vector<int> vals; vals.reserve(primes.size()); for(int p : primes){ if(p > N) break; // 安全判断 vals.push_back(N/p); } // 排序并去重 sort(vals.begin(), vals.end()); vals.erase(unique(vals.begin(), vals.end()), vals.end()); // 预先计算并保存 f(m) 的结果 // 用一个 map<int,long long> 存,或者用数组(但要注意 max 值). // 因为可能出现的 m = floor(N/p) <= N. 直接开数组也行 (下标到N)。 // 不过为了节省时间,这里只对出现的 m 计算。 unordered_map<int, long long> fMap; fMap.reserve(vals.size()*4); // 预留一些空间,避免频繁 rehash for(int m : vals) { fMap[m] = calcF(m); } // 最终答案 = sum_{p prime <= N} f( floor(N/p) ) long long answer = 0; for(int p : primes){ if(p > N) break; int m = N/p; answer += fMap[m]; } cout << answer << "\n"; return 0; }
主要要点:
computeMuAndPrimes
使用了线性筛法,可以在 或 左右的复杂度下同时得到:
spf[i]
: i
的最小质因子(若 spf[i]==0
则 i
是质数);mu[i]
: Möbius 函数;primes
: 从小到大的质数列表。prefixMu[i]
: 。后面分块时用前缀和快速求 。calcF(M)
函数是核心:它用到分块思路,只需大约 次循环即可完成 。calcF(m)
即可;之后对每个质数查询并累加。long long
(或 64 位)存储;答案可能接近 级别(如 ,答案量级可达 ,仍在 64 位范围内)。calcF(m)
约 。故这部分大约 。如有需要,可以使用更快的读写手段(scanf/printf
+ __attribute__((optimize))
等)以进一步加速。
本题的关键在于:
这样即可在 较大的情况下仍保持可行的性能。祝一切顺利!