问题描述
给定一个正整数 N,求所有满足以下条件的有序整数对 (x,y) 的数量:
- x 和 y 均为不大于 N 的正整数;
- x<y;
- x 和 y 的最大公约数大于 1 且小于 x。
输入格式
N
输出格式
输出满足条件的有序整数对 (x,y) 的数量。
样例输入
9
样例输出
3
评测数据规模
分析
前置知识
**因数:**又称约数,指能整除某整数的非零整数。
**eg:**12的因数:12=1*12=2*6=3*4,因此,12的因数有:1,2,3,4,6,12。
**质数:**也叫素数,只有1和它本身两个因数的数。
**eg:**1,2,3,5均是素数:因为1=1*1,2=1*2,3=1*3,5=1*5;4不是素数:因为4=1*4=2*2,它的因数除了1和它本身外还有一个2,因此不是素数。
**最大公约数(Greatest Common Divisor,gcd):**也叫最大公因数,就是两个数的共同因数中最大的那个数。
性质:
- 最大公约数的运算满足交换律,结果不受操作数顺序的影响,即gcd(a, b)=gcd(b,a);
- 最大公约数运算满足结合律,即gcd(a, gcd(b, c))=gcd(gcd(a, b), c);
- 最大公约数运算满足分配律,对于任意非零整数k,都有gcd(ka, kb)=|k|*gcd(a, b)。
定理:裴蜀定理(这里不涉及,就不拓展了)
**eg:**2的因数有:1,2,3,4,6,12;16的因数有:1,2,4,8,16。12和16共同的因数有:1,2,4;其中最大的为4,因此该数就称为12和16的最大公约数。
java
// 辗转相除法求最大公约数
public int gcd(int m, int n) {
if (n==0) return m; //此时m为最大公约数
return gcd(n,m%n);
}
tip:最大公约数*最小公倍数=m*n,因此该方法也可用于求最小公倍数(Least Common Multiple,lcm)。
**互质:**也叫互素,是公约数只有1的两个整数,即gcd(a,b)=1。
性质:
- 1和任何数都互质;
- 两个不同的质数一定互质;(如5与11)
- 相邻两个自然数一定互质;(相差为1,如11与12)
- 相邻两个奇数一定互质;(如3与5,7与9)
- 两个偶数一定不互质;(公约数至少为2)
- 较大数是质数的两个数是互质数;(如4与23)
- ......
欧拉函数
: 小于或等于n 的正整数中与n互质的数的数目。
计算方法:
1、先化为标准分解式形式
2、计算
eg1:
1、不是质数的要继续往下分解,直到拆成多个质数相乘的形式
此时,。
2、代入公式计算
。
验证一下是否正确:在[1, 8]中与8互质的数有:1,3,5,7;结果正确。
eg2:
1、。
2、。
题目分析
因为N的规模来到了,直接暴力枚举数对,再使用gcd(m,n)计算最大公约数必然会超时,因此得对其进行优化。
题目要求:设;
因为d的范围一直是相对固定的,为[2, x),而我们最开始的想法是直接枚举数对,数量太多,这是超时的原因,因此,可以考虑转而去枚举d,这样一来,只要知道一个d就可以知道有多少数对。
所以,设 x=da ,y=db ,其中 gcd(a,b)=1(因为gcd满足分配律,代入化简即可得) 且 a>=2(将x,y代入,得da>d,因为d>=2,因此可以直接约掉d,得a>1,即a>=2);
最终化简可以得到:,即求满足该条件的数对。
从上面的条件中看到:gcd(a,b)=1,是互质,而且题目只要求数量,不要求数对的内容,因此,完全可以将a或者b视为一个固定数,通过欧拉函数来求出与它互质的数的数量,这样就可以避免过多次调用gcd而导致超时问题。
假设固定b,则满足与b互质的数的数量为,因为欧拉函数计算出来的数量是包括1的,但是a又不能小于1,因此,最终满足与b互质的数的数量为
。
代码实现
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
// 埃氏筛求欧拉函数
long[] phi = new long[N + 1];
// 初始化:先把 phi[i] 存成数字本身,后面只需要依次乘以每个质因子的(1−1/p)就行
for (int i = 1; i <= N; i++) {
phi[i] = i;
}
// 求欧拉函数
for (int i = 2; i <= N; i++) {
if (phi[i] == i) { // i是质数:为什么只有质数才会进入循环?因为欧拉函数要求的是累乘质数,而合数不是任何数的质因子,完全不需要处理
for (int j = i; j <= N; j += i) { //j+=i:就是找到质数的所有倍数。一个质数(除了1以外的质数)的倍数一定不是质数
//非质数的数(合数):在遍历到其他质数时已经进行处理了
phi[j] = phi[j] / i * (i - 1); //补全它的欧拉函数参数,即乘上(i-1)/i
}
}
}
// 枚举d
long ans = 0;
for (int d = 2; d <= N; d++) {
int M = N / d; // 最大的b值
if (M < 3) continue; // 需要b>=3才能有a>=2
// 直接枚举b从3到M,累加 (phi[b] - 1)
for (int b = 3; b <= M; b++) {
ans += phi[b] - 1; // 与b互质且a∈[2,b-1]的个数
}
}
System.out.println(ans);
sc.close();
}
}
欧拉函数 = 数字本身 × (每个质因子分别算:(质因子 - 1)/ 质因子)
为什么只有质数才会进入计算?
合数在它的各个质因数处已经分别经过了处理。
拿
i=6(合数)举例:
- 初始化
phi[6]=6;i=2时:phi[6] = 6/2*1 = 3;i=3时:phi[6] = 3/3*2 = 2;phi [6] 已经正确,因为在质数2和质数3时,通过倍数的关系已经对6进行了处理。
当循环到
i=6,为合数,根据前面的欧拉定理,质数需要再拆解,而拆解结果在前面质数2,3已经处理过,因此,在此处不参与计算,跳过。

