📌  相关文章
📜  对于给定范围内的任何对,可能的最小乘积模 N(1)

📅  最后修改于: 2023-12-03 14:53:38.977000             🧑  作者: Mango

对于给定范围内的任何对,可能的最小乘积模 N

在程序设计中,我们有时需要对给定范围内的任何两个数进行操作,比如求它们的最小公倍数或最大公约数。而为了避免数值过大,我们常常需要对一些中间计算结果取模,这就是所谓的“取模运算”。

在这里,我们探讨的是另一个问题:对于给定范围内的任何对,如何计算它们的可能的最小乘积模 $N$?这个问题看似简单,却隐藏了许多技巧和优化,需要有一定的算法功底才能较好地解决。

问题描述

假设有一个正整数 $N$,一个区间 $[L,R]$,以及若干个二元组 $(a,b)$,其中 $\gcd(a,b)=1$,$L\le a,b\le R$。现在,希望找到所有二元组 $(a,b)$ 的乘积模 $N$ 的最小值。

具体地说,对于任意 $(a,b)$,我们需要计算 $ab\bmod N$,并找到此值的最小值。即:

$$\min_{L\le a,b\le R,\gcd(a,b)=1}ab\bmod N$$

解决方法

算法 1

首先,我们可以暴力枚举所有的二元组 $(a,b)$,并计算它们的乘积模 $N$。具体地,对于每个 $(a,b)$,我们可以计算 $ab$ 的值,然后对 $N$ 取模,即可得到 $ab\bmod N$ 的值。最后,我们在所有计算结果中选出最小值即可。

这个算法的时间复杂度为 $O((R-L+1)^2)$,显然太过暴力,对于大规模数据效率较低,无法通过本题。

算法 2

下面,我们尝试一种更加高效的算法。我们可以发现,$(a,b)$ 和 $(b,a)$ 的乘积模 $N$ 的结果是相同的。因此,我们只需要枚举所有满足 $a\le b$ 的二元组 $(a,b)$,即可得到所有需要考虑的二元组。

同时,我们可以根据乘法交换律,只枚举 $a\le b$ 时的情况,然后将结果乘以 2 即可。

根据这种思路,我们可以将总的枚举量减少为 $O((R-L+1)^2/2)$。

然而,这种算法的时间复杂度仍然很高,需要进一步的优化。

算法 3

我们再来进一步优化算法。事实上,对于两个正整数 $a,b$,它们的乘积 $ab$ 可以被分解为 $ab=k\cdot g$ 的形式,其中 $k=\gcd(a,b)$,$g=\mathrm{lcm}(a,b)$。

因此,我们可以将 $(a,b)$ 分解为 $(ka',kb')$ 的形式,其中 $\gcd(a',b')=1$。显然,这两个新的数对 $(a',b')$ 和 $(b',a')$ 的乘积模 $N$ 的值是相同的。因此,我们只需要枚举所有互质的数对 $(a',b')$,然后再将它们分别乘以 $k$,即可得到原来的数对 $(a,b)$。

设最小的乘积模 $N$ 的值为 $P$。对于枚举到的数对 $(a',b')$,设 $ap\equiv 1\pmod{b'}$,$bp\equiv 1\pmod{a'}$,则 $k\le\max{a',b'}$,而 $g\ge\min{a',b'}\cdot p$。

因此,我们可以按照 $a'$ 从小到大的顺序枚举互质的数对 $(a',b')$,然后维护一个变量 $p$,表示 $b'p$ 模 $a'$ 的逆元。对于每个 $(a',b')$,我们根据 $b'p\equiv 1\pmod{a'}$ 的形式更新 $p$,然后计算它与 $a'$ 以及 $b'$ 的乘积模 $N$ 的值,并将结果与 $P$ 比较。这样,我们最终得到的 $P$ 就是最小的乘积模 $N$ 的值。

该算法的时间复杂度为 $O((R-L+1)\log(R-L+1))$。因此,我们可以快速地解决本题。

代码实现

下面给出 Java 语言的代码实现。其中,我们使用了一个名为 $\Lambda$ 的函数式接口,用于指定需要计算的运算。

import java.util.function.LongBinaryOperator;

public class MinProductModN {
    static long INF = (long) 1e18;

    static long mod(long x, long m) { return (x % m + m) % m; }

    static long inv(long a, long m) {
        // 求 a 在模 m 意义下的逆元
        long b = m, x = 0, y = 1;
        while (a != 0) {
            long t = b / a;
            b -= t * a; long tmp = x - t * y; x = y; y = tmp;
            tmp = a; a = b; b = tmp;
        }
        if (x < 0) x += m / a;
        return x;
    }

    static long gcd(long a, long b) { return b == 0 ? a : gcd(b, a % b); }

    static long lcm(long a, long b) { return a / gcd(a, b) * b; }

    static long minProductModN(int L, int R, long N, LongBinaryOperator op) {
        // 根据 op 指定的运算,计算 mod N 意义下最小的乘积
        long ans = INF;
        int tot = 0;
        for (int i = L; i <= R; i++) {
            for (int j = i + 1; j <= R; j++) {
                if (gcd(i, j) != 1) continue;
                long res = op.applyAsLong(i, j);
                if (res < ans) {
                    ans = res;
                }
                tot++;
            }
        }
        return ans;
    }

    static long minProductModN2(int L, int R, long N, LongBinaryOperator op) {
        // 优化后的算法 2:先枚举小的数,再乘以 2
        long ans = INF;
        int tot = 0;
        for (int i = L; i <= R; i++) {
            for (int j = i; j <= R; j++) {
                if (gcd(i, j) != 1) continue;
                long res = op.applyAsLong(i, j);
                if (res < ans) {
                    ans = res;
                }
                tot++;
            }
        }
        return ans * 2;
    }

    static long minProductModN3(int L, int R, long N, LongBinaryOperator op) {
        // 优化后的算法 3:按照互质数对的方式枚举
        long ans = INF;
        for (int i = L; i <= R; i++) {
            for (int j = i + 1; j <= R; j++) {
                if (gcd(i, j) != 1) continue;
                long k = gcd(i, j);
                long g = lcm(i, j);
                long a = i / k, b = j / k;
                long p = inv(b, a);
                long q = mod(p * b, a);
                assert q == 1; // 确保计算的逆元是正确的
                long x = op.applyAsLong(k * a, k * b);
                long y = mod(k * g, N);
                long z = mod(x * y, N);
                if (z < ans) {
                    ans = z;
                } 
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        int L = 1, R = 100000;
        long N = (long) 1e9 + 7;
        LongBinaryOperator op = (a, b) -> mod(a * b, N);
        long ans1 = minProductModN(L, R, N, op);
        long ans2 = minProductModN2(L, R, N, op);
        long ans3 = minProductModN3(L, R, N, op);
        System.out.println(ans1 + " " + ans2 + " " + ans3);
    }
}
总结

以上,我们介绍了对于给定范围内的任何对,可能的最小乘积模 $N$ 的求解方法。具体来说,我们介绍了三种不同的算法,包括暴力枚举、优化后的暴力算法、以及按照互质数对的方式枚举。

通过不断地优化算法,我们可以将时间复杂度由 $O((R-L+1)^2)$ 降低到 $O((R-L+1)\log(R-L+1))$,进而在给定的时间限制内有效地解决本题。同时,我们也可以通过这个问题的求解,加深对于数论、模运算、算法设计等方面的理解,为算法竞赛和实际问题的求解打下坚实的基础。