有序整数对个数-欧拉函数

问题描述

给定一个正整数 N,求所有满足以下条件的有序整数对 (x,y) 的数量:

  1. x 和 y 均为不大于 N 的正整数;
  2. x<y;
  3. 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):**也叫最大公因数,就是两个数的共同因数中最大的那个数。

性质:

  1. 最大公约数的运算满足交换律,结果不受操作数顺序的影响,即gcd(a, b)=gcd(b,a);
  2. 最大公约数运算满足结合律,即gcd(a, gcd(b, c))=gcd(gcd(a, b), c);
  3. 最大公约数运算满足分配律,对于任意非零整数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. 1和任何数都互质;
  2. 两个不同的质数一定互质;(如5与11)
  3. 相邻两个自然数一定互质;(相差为1,如11与12)
  4. 相邻两个奇数一定互质;(如3与5,7与9)
  5. 两个偶数一定不互质;(公约数至少为2)
  6. 较大数是质数的两个数是互质数;(如4与23)
  7. ......

欧拉函数 小于或等于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(合数)举例:

  1. 初始化 phi[6]=6;
  2. i=2时:phi[6] = 6/2*1 = 3;
  3. i=3时phi[6] = 3/3*2 = 2;

phi [6] 已经正确,因为在质数2和质数3时,通过倍数的关系已经对6进行了处理。

当循环到 i=6,为合数,根据前面的欧拉定理,质数需要再拆解,而拆解结果在前面质数2,3已经处理过,因此,在此处不参与计算,跳过。

相关推荐
lifewange2 小时前
Java 自动化测试参数化实现
java·数据库·sqlserver
码上农民2 小时前
Idea2025.3.3专业版安装和无限试用
java·ide·intellij-idea
CDN3602 小时前
CDN 回源异常、源站压力大?负载均衡与回源策略优化
java·运维·负载均衡
ywlovecjy2 小时前
怎么下载安装yarn
java
凌冰_2 小时前
异常: Can not set java.lang.Double field org.hlx.my2.pojo.Book.price
java·开发语言
dazzle3 小时前
机器学习算法原理与实践-入门(十):基于PaddlePaddle框架的线性回归
算法·机器学习·paddlepaddle
计算机徐师兄3 小时前
Java基于SSM的文玩销售小程序【附源码、文档说明】
java·小程序·文玩销售小程序·文玩销售·java文玩销售小程序·文玩销售微信小程序·java文玩销售微信小程序
2501_940315263 小时前
【无标题】1.用哈希表做两数之和
算法·哈希算法·散列表
mOok ONSC3 小时前
Spring Boot 3.4 正式发布,结构化日志!
java·spring boot·后端