041 最大公约数 最小公倍数 同余原理

力扣 878: 第 N 个神奇数字,同余原理,这是处理大数运算的重要工具

Posted by Hilda on March 14, 2025

求最大公约数

1) 欧几里得算法的过程 : 辗转相除法 2) 正确性的证明过程见代码注释部分,润色的证明过程非常好懂,不过直接记忆过程即可 3) 求gcd(a,b),其中a>b,时间复杂度为O((log a)的3次方),时间复杂度证明略,这个复杂度足够好了 4) 简单转化就可以求最小公倍数 5) 更高效求最大公约数的Stein算法、由最大公约数扩展出的“裴蜀定理”,比赛同学有兴趣可以继续研究 6) 不比赛的同学,哪怕你的目标是最顶级的公司应聘、还是考研,掌握这个只有一行的函数已经足够!

【总结:】

1.求最大公约数:时间复杂度是\((log(a))^3\),欧几里得算法/辗转相除法的复杂度足够好了

1
2
3
public long gcd(long a, long b) {
    return b == 0 ? a : gcd(b, a % b);
}

2.求最小公倍数:

1
2
3
public long lcm(long a, long b) {
    return a / gcd(a, b) * b;
}

力扣878-第 N 个神奇数字

878. 第 N 个神奇数字

题目

一个正整数如果能被 ab 整除,那么它是神奇的。

给定三个整数 n , a , b ,返回第 n 个神奇的数字。因为答案可能很大,所以返回答案 10^9 + 7 取模 后的值。

示例 1:

1
2
输入:n = 1, a = 2, b = 3
输出:2

示例 2:

1
2
输入:n = 4, a = 2, b = 3
输出:6

提示:

  • 1 <= n <= 109
  • 2 <= a, b <= 4 * 104

解法

思路:二分答案法+容斥原理

要知道\(0~x\)范围上有多少个神奇数字,根据容斥原理,只需要计算\(x/a+x/b-x/lcm(a,b)\)。

根据“二分答案法”,只需要在\([0,n*min(a,b)]\)上寻找。其中n是要寻找的神奇数字的个数。这也很好理解。

代码如下:

注:其中用到的中间的变量都是long类型,不然提交通过不了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
    public int nthMagicalNumber(int n, int a, int b) {
        long l = 0, r = (long) n * Math.min(a, b);//范围
        long m, ans = 0;
        while (l <= r) {
            m = l + ((r - l) >> 1);
            if (m / a + m / b - m / (lcm(a, b)) >= n){
                ans = m;
                r = m - 1;
            }else {
                l = m + 1;
            }
        }
        return (int)(ans % (1000000007));     
    }
    public long gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    public long lcm(int a, int b) {
        return a / gcd(a, b) * b;
    }
}

同余原理

  • 介绍背景
  • 加法、乘法每一步计算完后直接取模,减法则为(a-b+m)%m
  • 要确保过程中不溢出,所以往往乘法运算的用long类型做中间变量
  • 除法的同余需要求逆元,会在【必备】课程里讲述,较难的题目才会涉及

下面是关于上面内容的一些测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.math.BigInteger;

public class SameMod {
    // 为了测试
    public static long random() {
        return (long) (Math.random() * Long.MAX_VALUE);
    }

    // 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果
    public static int f1(long a, long b, long c, long d, int mod) {
        BigInteger o1 = new BigInteger(String.valueOf(a)); // a
        BigInteger o2 = new BigInteger(String.valueOf(b)); // b
        BigInteger o3 = new BigInteger(String.valueOf(c)); // c
        BigInteger o4 = new BigInteger(String.valueOf(d)); // d
        BigInteger o5 = o1.add(o2); // a + b
        BigInteger o6 = o3.subtract(o4); // c - d
        BigInteger o7 = o1.multiply(o3); // a * c
        BigInteger o8 = o2.multiply(o4); // b * d
        BigInteger o9 = o5.multiply(o6); // (a + b) * (c - d)
        BigInteger o10 = o7.subtract(o8); // (a * c - b * d)
        BigInteger o11 = o9.add(o10); // ((a + b) * (c - d) + (a * c - b * d))
        // ((a + b) * (c - d) + (a * c - b * d)) % mod
        BigInteger o12 = o11.mod(new BigInteger(String.valueOf(mod)));
        if (o12.signum() == -1) {
            // 如果是负数那么+mod返回
            return o12.add(new BigInteger(String.valueOf(mod))).intValue();
        } else {
            // 如果不是负数直接返回
            return o12.intValue();
        }
    }

    // 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果
    public static int f2(long a, long b, long c, long d, int mod) {
        int o1 = (int) (a % mod); // a
        int o2 = (int) (b % mod); // b
        int o3 = (int) (c % mod); // c
        int o4 = (int) (d % mod); // d
        int o5 = (o1 + o2) % mod; // a + b
        int o6 = (o3 - o4 + mod) % mod; // c - d
        int o7 = (int) (((long) o1 * o3) % mod); // a * c
        int o8 = (int) (((long) o2 * o4) % mod); // b * d
        int o9 = (int) (((long) o5 * o6) % mod); // (a + b) * (c - d)
        int o10 = (o7 - o8 + mod) % mod; // (a * c - b * d)
        int ans = (o9 + o10) % mod; // ((a + b) * (c - d) + (a * c - b * d)) % mod
        return ans;
    }

    public static void main(String[] args) {
        System.out.println("测试开始");
        int testTime = 100000;
        int mod = 1000000007;
        for (int i = 0; i < testTime; i++) {
            long a = random();
            long b = random();
            long c = random();
            long d = random();
            if (f1(a, b, c, d, mod) != f2(a, b, c, d, mod)) {
                System.out.println("出错了!");
            }
        }
        System.out.println("测试结束");

        System.out.println("===");
        long a = random();
        long b = random();
        long c = random();
        long d = random();
        System.out.println("a : " + a);
        System.out.println("b : " + b);
        System.out.println("c : " + c);
        System.out.println("d : " + d);
        System.out.println("===");
        System.out.println("f1 : " + f1(a, b, c, d, mod));
        System.out.println("f2 : " + f2(a, b, c, d, mod));
    }
}

image-20250314114625327