GCD

数论进阶笔记:同余等价与扩展欧几里得算法

1. 同余等价与差分 GCD 模型

来源文件106396E.cpp

  • 🏷️ 题面抽象
    给定两个长度为 $n$ 的数组 $A$ 和 $B$。求一个最大的正整数 $k$,使得对于所有的 $i \in [1, n]$,都满足 $A_i \equiv B_i \pmod k$。
    (等价表述:每次可以对 $A_i$ 加上或减去任意次 $k$,求能使数组 $A$ 完全变成数组 $B$ 的最大操作步长 $k$。)

  • 💡 核心思路

  • 同余性质转化:根据同余定理,如果 $A_i \equiv B_i \pmod k$,那么它们的差值必定是 $k$ 的倍数,即 $(A_i - B_i) \equiv 0 \pmod k$。

  • 提取绝对差值:我们可以构造一个差值数组 $C$,其中 $C_i = |A_i - B_i|$。此时问题转化为:$k$ 必须是所有 $C_i$ 的约数。

  • 求解最大步长:既然 $k$ 是所有差值的公约数,那么要求最大的 $k$,显然就是求整个差值数组 $C$ 的最大公约数 (GCD)

  • ⚠️ 细节与避坑指南

  • 初始求 GCD 的累加器可以设为 0(即代码中的 ans = 0),因为 $\gcd(0, x) = x$,这种写法非常简洁安全。

  • 特判全等情况:如果数组 $A$ 和 $B$ 完全一模一样,所有的差值都是 0,最终求得的 GCD 也会是 0。在数学上这意味着 $k$ 可以是无穷大。做题时需注意题目对于这种情况是要求输出 0 还是其它特定标记。

点击查看代码
void solve() {
    int n;
    cin >> n;
    vi a(n + 1);
    vi b(n + 1);
    vi c(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];
    for (int i = 1; i <= n; i++) {
        c[i] = abs(a[i] - b[i]);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        ans = gcd(ans, c[i]);
    }
    cout << ans << endl;
}


2. 线性同余方程与 EXGCD 模型 (青蛙的约会)

来源文件P_1516_青蛙的约会.cpp

  • 🏷️ 题面抽象
    两只青蛙在一个周长为 $L$ 的圆周上,分别从坐标 $x$ 和 $y$ 出发,每次分别跳 $m$ 和 $n$ 步。问最少跳多少次能够相遇?如果永远不能相遇输出 Impossible
  • 💡 核心思路
  • 转化为一元线性同余方程:设跳了 $t$ 次后相遇,那么满足同余式:$x + m \cdot t \equiv y + n \cdot t \pmod L$。
  • 移项化简:$(m - n) \cdot t \equiv (y - x) \pmod L$。
  • 转化为不定方程:将其转化为二元一次方程形式:$(m - n) \cdot t - L \cdot k = (y - x)$。令 $A = m - n$, $B = L$, $C = y - x$,即可化为标准形式 $Ax + By = C$。这就是扩展欧几里得算法(EXGCD)的经典应用场景。
点击查看代码
// exgcd

int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}
void solve() {
    int x, y, m, n, l;
    cin >> x >> y >> m >> n >> l;
    int a = m - n;
    int b = l;
    int c = y - x;
    int x0, y0;
    if (a < 0) {
        a = -a;
        c = -c;
    }
    int g = exgcd(a, b, x0, y0);
    int x1 = x0 * (c / g);
    int y1 = y0 * (c / g);

    if (c % g != 0) {
        cout << "Impossible" << endl;
        return;
    }

    int step = b / g;

    int ans = (x1 % step + step) % step;

    if (ans == 0) ans = step;

    cout << ans << endl;
}

  • ⚠️ 细节与避坑指南
  • 保证 $A$ 为正数:在代码处理中,如果 $A = m - n < 0$,需要将其取反,同时把等式右边的 $C = y - x$ 也取反,以避免 EXGCD 处理负数时产生符号混乱或逻辑错误。
  • 无解判定 (裴蜀定理):方程 $Ax + By = C$ 有解的充要条件是 $C$ 必须是 $\gcd(A, B)$ 的倍数。因此当 C % g != 0 时,必定无法相遇,直接输出 Impossible
  • 求最小正整数解:EXGCD 求出的 $x_0$ 只是方程 $Ax + By = \gcd(A, B)$ 的一个特解。对于原方程,特解扩大相应的倍数即为 $x_1 = x_0 \times (C / g)$。
  • 防止负数取模:方程解的循环步长为 $step = B / g$。为了求出最小的正整数时间 $t$,需要对 $step$ 取模。C++ 中的负数取模依然是负数,必须使用 (x1 % step + step) % step 的经典技巧将其映射回正区间。
posted @ 2026-05-16 19:54  r_123  阅读(14)  评论(0)    收藏  举报