FXJ Wiki

Back

数论(I)Blur image

gcd#

辗转相除法:gcd(a,b)=gcd(b,amodb)\gcd(a, b) = \gcd(b, a \bmod b),递归直到 b=0b = 0

int gcd(int a, int b) {
    return !b ? a : gcd(b, a % b);
}
cpp

C++17 起可用 std::gcd(a, b),早期版本可用 __gcd(a, b)

lcm#

gcd(a,b)×lcm(a,b)=a×b\gcd(a, b) \times \operatorname{lcm}(a, b) = a \times b,先求 gcd 再 O(1)O(1) 得 lcm。

多个数的 lcm:两两合并即可,不需要先求整体的 gcd。

int lcm(int a, int b) {
    return a / gcd(a, b) * b;  // 先除后乘,防溢出
}
cpp

裴蜀定理#

a,ba, b 是不全为零的整数,则存在整数 x,yx, y 使得 ax+by=gcd(a,b)ax + by = \gcd(a, b)

证明:设 s=ax0+by0s = ax_0 + by_0ax+byax + by 能取到的最小正整数。

  • gcd(a,b)a\gcd(a, b) \mid agcd(a,b)b    gcd(a,b)s\gcd(a, b) \mid b \implies \gcd(a, b) \mid s
  • a=qs+r  (0r<s)a = qs + r\;(0 \le r < s),则 r=aqs=aq(ax0+by0)=a(1qx0)+b(qy0)r = a - qs = a - q(ax_0 + by_0) = a(1 - qx_0) + b(-qy_0)。由于 ss 是最小正整数,r=0r = 0,故 sas \mid a。同理 sbs \mid b,所以 sgcd(a,b)s \mid \gcd(a, b)

因此 s=gcd(a,b)s = \gcd(a, b)

逆定理:若存在 x,yx, y 使 ax+by=dax + by = d,且 dda,ba, b 的正公因数,则 d=gcd(a,b)d = \gcd(a, b)。特别地,ax+by=1    gcd(a,b)=1ax + by = 1 \iff \gcd(a, b) = 1

推论a,ba, b 互素时,ababab - a - b 是最大的不能用 ax+byax + byx,yx, y 为非负整数)表示的正整数。例如 P3951 直接输出 ababab - a - b

扩展欧几里得#

用于求 ax+by=gcd(a,b)ax + by = \gcd(a, b) 的一组整数解。

设递归求解 gcd(b,amodb)\gcd(b, a \bmod b) 时得到 bx2+(amodb)y2=gcdbx_2 + (a \bmod b)y_2 = \gcd,代入 amodb=aa/bba \bmod b = a - \lfloor a/b \rfloor \cdot b

bx2+(aa/bb)y2=ay2+b(x2a/by2)bx_2 + (a - \lfloor a/b \rfloor \cdot b)y_2 = ay_2 + b(x_2 - \lfloor a/b \rfloor y_2)

对照 ax1+by1ax_1 + by_1,得 x1=y2,  y1=x2a/by2x_1 = y_2,\; y_1 = x_2 - \lfloor a/b \rfloor y_2。递归边界 b=0b = 0x=1,y=0x = 1, y = 0

int exgcd(int a, int b, int &x, int &y) {
    if (!b) { x = 1; y = 0; return a; }
    int d = exgcd(b, a % b, x, y);
    int t = x;
    x = y;
    y = t - (a / b) * y;
    return d;
}
cpp

通解:求出特解 (x0,y0)(x_0, y_0) 后,x=x0+kbgcd,  y=y0kagcdx = x_0 + k \cdot \frac{b}{\gcd},\; y = y_0 - k \cdot \frac{a}{\gcd}

快速幂#

计算 xymodpx^y \bmod pO(logy)O(\log y)

int qpow(int x, int y, int p) {
    int ans = 1;
    while (y) {
        if (y & 1) ans = 1ll * ans * x % p;
        x = 1ll * x * x % p;
        y >>= 1;
    }
    return ans;
}
cpp

注意 xxpp 较大时中间乘法用 long long

逆元#

aa 在模 bb 下的逆元 a1a^{-1} 满足 aa11(modb)a \cdot a^{-1} \equiv 1 \pmod b,等价于解 ax1(modb)ax \equiv 1 \pmod b

费马小定理#

bb 为质数时,a1ab2(modb)a^{-1} \equiv a^{b-2} \pmod b。直接快速幂即可。

扩展欧几里得#

对任意模数,解 ax+by=1ax + by = 1,所得 xx 即为逆元。适用于 bb 不一定是质数的情况。

线性递推#

11nn 所有数模 pp 的逆元,O(n)O(n)

inv[1] = 1;
for (int i = 2; i <= n; ++i)
    inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
cpp

推导:p=p/ii+(pmodi)p = \lfloor p/i \rfloor \cdot i + (p \bmod i),两边模 pp 整理即得。

任意 nn 个数的逆元#

a1,,ana_1, \dots, a_n 各自的逆元。先算前缀积 sis_i,快速幂求 sn1s_n^{-1},再倒推:

s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = 1ll * s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2, p);
for (int i = n; i >= 1; --i) {
    sv[i - 1] = 1ll * sv[i] * a[i] % p;
    inv[i] = 1ll * sv[i] * s[i - 1] % p;
}
cpp

O(n+logp)O(n + \log p) 求出全部逆元。

中国剩余定理#

求解同余方程组:

{xr1(modm1)xr2(modm2)xrn(modmn)\begin{cases} x \equiv r_1 \pmod{m_1} \\ x \equiv r_2 \pmod{m_2} \\ \cdots \\ x \equiv r_n \pmod{m_n} \end{cases}

其中 mim_i 两两互质。

M=miM = \prod m_ici=M/mic_i = M / m_idid_icic_i 在模 mim_i 下的逆元。则:

xi=1nricidi(modM)x \equiv \sum_{i=1}^n r_i \cdot c_i \cdot d_i \pmod M
long long crt(const vector<int>& r, const vector<int>& m) {
    long long M = 1, ans = 0;
    for (int mi : m) M *= mi;
    for (int i = 0; i < (int)r.size(); ++i) {
        long long ci = M / m[i];
        int x, y;
        exgcd(ci % m[i], m[i], x, y);
        long long di = (x % m[i] + m[i]) % m[i];
        ans = (ans + r[i] * ci % M * di) % M;
    }
    return ans;
}
cpp

模板题:P1495 曹冲养猪

数论(I)
https://fxj.wiki/blog/algorithm-number-theory-1
Author 玛卡巴卡
Published at 2023年10月23日
Comment seems to stuck. Try to refresh?✨