算法学习笔记(9): 中国剩余定理(CRT)以及其扩展(EXCRT)

算法,学习,笔记,中国,剩余,定理,crt,以及,扩展,excrt · 浏览次数 : 87

小编点评

**内容简介** 这篇文章包含对逆元及其应用的介绍,以及对 EXCRT 的部分内容的补充。 **逆元及其应用** 逆元是用于求解不定方程 ax + by = c 的解的算法。常用的逆元算法包括: * **贝祖定理**:如果 gcd(x, y) | c 则有解,否则无解 * **扩展中国剩余定理** (EXCRT):对于不定方程 ax + by = c,如果 gcd(x, y) | c,则有解,否则无解 **EXCRT** EXCRT 是一个用于求解不定方程 ax + by = c 的算法。 EXCRT 的主要步骤包括: * **求解 gcd(x, y)** * **根据 gcd(x, y) 计算 s 和 t** * **求解 x = (a + s * a.p % lcm) % lcm** * **求解 y = (b - s * a.p % lcm) % lcm** * **判断有解** **补充** * EXCRT 的部分内容中包含对模运算的介绍,但由于文章篇目限制,这里没有展开该部分内容。 * EXCRT 的部分内容中包含对贝祖定理的介绍,但由于文章篇目限制,这里没有展开该部分内容。

正文

扩展中国剩余定理

讲解扩展之前,我们先叙述一下普通的中国剩余定理

“China Remain Theory” 也叫做孙子定理

难得是以中国命名的定理,谁敢不掌握

中国剩余定理

中国剩余定理通过一种非常精巧的构造求出了一个可行解

但是毕竟是构造,所以相对较复杂

\[\begin{cases} x \equiv a_1 \pmod{m_1} \\ x \equiv a_2 \pmod{m_2} \\ x \equiv a_3 \pmod{m_3} \\ \dots \\ x \equiv a_n \pmod{m_n} \end{cases} \]

对于上述同余方程组,首先需要满足 \(m_1, m_2, m_3, \dots, m_n\) 互质

\(m = \prod_{i=1}^{n} m_i, M_i = m / m_i,\ t_i\) 是线性同余方程 \(M_it_i \equiv 1 \pmod {m_i}\) 的一个解

那么上述方程组有整数解,为 \(x = \sum_{i=1}^n a_iM_it_i\)


证明:

由于所有 \(m_i\) 互质,所以 \(M_i = m/m_i\) 是除了 \(m_i\) 之外的所有模数的倍数,所以 \(\forall j \ne i, a_iM_it_i \equiv 0 \pmod {m_k}\)

所以代入 \(x = \sum_{i=1}^n a_iM_it_i\),原方程组成立


中国剩余定理给出了方程的一个特解。通解可以表示为 \(x + km (k \in \Z)\)。如果需要求最小的非负整数解,只需要让 \(x\) 对于 \(m\) 取模就是。

参考代码

【模板】中国剩余定理(CRT)/ 曹冲养猪 - 洛谷

#include <iostream>

typedef long long ll;

ll m[11], a[11], Mm[11], y[11];

// 扩展欧几里得算法,由于求出线性同余方程 Mi * ti = 1 (mod p) 的一个特解
// 上述同余式可以转化为 Mi * ti + k * p = 1
// 所以跑一个 extgcd(Mi, p, ti, k) 即可 (k没有用的)
void extgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1, y = 0;
        return; 
    }
    extgcd(b, a % b, y, x);
    y -= (a / b) * x;
}

int main() {
    int n; scanf("%d", &n);

    ll M(1);
    // 这里读入,并求出m
    for (int i = 0; i < n; i++) {
        scanf("%lld%lld", m + i, a + i);
        M *= m[i];
    }

    // 求出Mi
    for (int i = 0; i < n; i++) {
        Mm[i] = M / m[i];
    }

    // 求出每一个不定方程的特解
    for (int i = 0; i < n; i++) {
        ll x, tmp, mi = m[i];
        extgcd(Mm[i], mi, x, tmp);
        y[i] = (x + mi) % mi;
    } 

    // 求和获得最小正整数解
    ll x(0);
    for (int i = 0; i < n; i++) {
        x = (x + a[i] * Mm[i] * y[i]) % M;
    }

    printf("%lld\n", x);
    return 0;
}

扩展中国剩余定理

还是考虑上述式子

\[\begin{cases} x \equiv a_1 \pmod{m_1} \\ x \equiv a_2 \pmod{m_2} \\ x \equiv a_3 \pmod{m_3} \\ \dots \\ x \equiv a_n \pmod{m_n} \end{cases} \]

此时,我们不保证 \(m_i\) 不一定两两互质,所以中国剩余定理不再适用

所以扩展中国剩余定理就开始发挥作用了

虽然说是扩展……但是两者思路很不一样

扩展中国剩余定理采用数学归纳法,或者说两两合并法

假如我们需要合并方程

\[x \equiv a_1 \pmod {m_1} \]

\[x \equiv a_2 \pmod {m_2} \]

我们将同余式改写

\[k_1m_1 + a_1 = x \]

\[k_2m_2 + a_2 = x \]

将两者相减,整理后可得

\[k_1m_1 - k_2m_2 = a_2 - a_1 \]

由于我们已知 \(m_1, m_2, a_1, a_2\),也就是说我们只需要知道 \(k_1, k_2\) 其中任意一个,就可以求出这时的 \(x\) 是个什么东西了

所以,联想到了……扩展欧几里得算法

可以参考文章:算法学习笔记(1): 欧几里得算法及其扩展

我们令

\[\begin{aligned} d &= gcd(m_1, m_2) \\ c &= a_2 - a_1 \end{aligned} \]

那么化简一下有:\(k_1 m_1 - k_2 m_2 = c\)

所以我们求出 \(k_1' \frac {m_1}d + k_2' \frac {m_2}d = 1\)

那么换回去之后

\[\begin{aligned} k_1 &= k_1' \frac cd \\ k_2 &= -k_2' \frac cd \end{aligned} \]

由于 \(exgcd(a, b, x, y)\) 求出的是 \(xa + yb = \gcd(a, b)\) 的一组解……所以需要 \(\frac cd\)

\(l = lcm(m_1, m_2) = m_1 m_2 / \gcd(m_1, m_2)\)

那么此时 \(x \equiv k_1' m_1\frac cd + a_1 \pmod l\)

也就是可以得到 \(x\) 的一个解集 \(x \in X = \{kl + x|k \in \R\}\)

也就是说,我们将上述两个式子合并成了

\[x \equiv k_1' \frac cd m_1 + a_1 \pmod {lcm(m_1, m_2)} \]

那么考虑代码怎么写

typedef long long lint;

struct Equation {
    lint a, p;
};

inline lint gcd(lint x, lint y) {
    return y ? gcd(y, x % y) : x;
}

inline lint exgcd(lint x, lint y, lint &s, lint &t) {
    if (!y) {
        s = 1, t = 0;
        return x;
    }
    lint r = exgcd(y, x % y, t, s);
    t -= (x / y) * s;
    return r;
}

bool merge(const Equation &a, const Equation &b, Equation &res) {
    // 第一部分:预先求出需要的值
    lint d = gcd(a.p, b.p), s, t;
    lint lcm = a.p / d * b.p;

    // 第二部分:求出上文中的 k1', k2'
    lint c = (b.a - a.a) % lcm;
    if (c < 0) c += lcm;
    if (c % d) return false; // 判断有无解,返回false则无解
    exgcd(a.p / d, b.p / d, s, t);

    // 第三部分:求出上文中的 k1
    s = s * (c / d) % lcm;
    if (s < 0) s += lcm;

    // 第四部分:求出一个特殊的解
    lint x = (a.a + s * a.p % lcm) % lcm;
    if (x < 0) x += lcm;
    res = {x, lcm};
    return true;
}

?:为什么在前三个部分放在了模 \(lcm\) 的情况下计算

其实最小的正整数解在 \(\mod lcm\) 的情况下是唯一的~

唯一性的证明可以参考: 阮行止 的博客

所以放在\(\mod lcm\) 的条件下不会对合并造成影响,并且还保证了求出的解是最小非负整数解。

?: 为什么在第一部分需要提前算出 \(\gcd(m_1, m_2)\)

其实是不用的……只需要 data s, t; data d = exgcd(m1, m2, s, t);也可以算出正确答案……

?: 判断有解是如何判断的

有无解实际上就是拓展欧几里得算法有无解

而判断拓欧算法有无解,也就通过贝祖定理验证

也就是说,对于不定方程 ax + by = c

如果 gcd(x, y) | c 则有解,否则无解

【模板】扩展中国剩余定理(EXCRT) - 洛谷

NOTICE:在模板题上,由于可能会溢出……至于什么地方需要用龟速乘,请读者自行揣度

关于龟速乘:算法学习笔记(2): 逆元及其应用我提过一点

更详细的参考 快速乘总结 - 一只不咕鸟

后记

后来才发现,原来写的都是在胡扯……所以重新写了 exCRT 部分。好多都被网上的一些文章误导了,他们在 exCRT 前三个部分很多都是在\(\mod p_2\) 意义下进行的。虽然这样无可厚非,但是在判断有无解时会出现问题,所以……

与算法学习笔记(9): 中国剩余定理(CRT)以及其扩展(EXCRT)相似的内容:

算法学习笔记(9): 中国剩余定理(CRT)以及其扩展(EXCRT)

# 扩展中国剩余定理 [TOC] 讲解扩展之前,我们先叙述一下普通的中国剩余定理 > “China Remain Theory” 也叫做**孙子定理** > > 难得是以中国命名的定理~~,谁敢不掌握~~ ## 中国剩余定理 > 中国剩余定理通过一种非常精巧的构造求出了一个可行解 > > 但是毕竟是

算法学习笔记(6): 树链剖分

树链剖分 树链剖分是一个很神奇,但是在树上可以完成一些区间操作问题 简单来说,就是把一棵树分成一条条的链,通过维护链上的信息来维护整棵树的信息 基础知识可以参考我的另外一篇博客:算法学习笔记(5): 最近公共祖先(LCA) 这里假设你已经掌握了上述博客中的所有相关知识,并清晰了其背后的原理 性质?发

算法学习笔记(11): 原根

原根 此文相对困难,请读者酌情食用 在定义原根之前,我们先定义其他的一点东西 阶 通俗一点来说,对于 $a$ 在模 $p$ 意义下的阶就是 $a^x \equiv 1 \pmod p$ 的最小正整数解 $x$ 或者说,$a$ 在模 $p$ 意义下生成子群的阶(群的大小) 再或者说,是 $a$ 在模

算法学习笔记(30):Kruskal 重构树

Kruskal 重构树 这是一种用于处理与最大/最小边权相关的一个数据结构。 其与 kruskal 做最小生成树的过程是类似的,我们考虑其过程: 按边权排序,利用并查集维护连通性,进行合并。 如果我们在合并时,新建一个节点,其权值为当前处理的边的权值,并将合并的两个节点都连向新建的节点,那么就可以得

算法学习笔记(3.1): ST算法

ST表 在RMQ(区间最值)问题中,著名的ST算法就是倍增的产物。ST算法可以在 \(O(n \log n)\) 的时间复杂度能预处理后,以 \(O(1)\) 的复杂度在线回答区间 [l, r] 内的最值。 当然,ST表不支持动态修改,如果需要动态修改,线段树是一种良好的解决方案,是 \(O(n)\

C++算法之旅、09 力扣篇 | 常见面试笔试题(上)算法小白专用

算法学习笔记,记录容易忘记的知识点和难题。详解时空复杂度、50道常见面试笔试题,包括数组、单链表、栈、队列、字符串、哈希表、二叉树、递归、迭代、分治类型题目,均带思路与C++题解

C++算法之旅、08 基础篇 | 质数、约数

算法学习笔记,记录容易忘记的知识点和难题。试除法、分解质因数、筛质数、约数个数、约数之和、最大公约数

算法学习笔记(1): 欧几里得算法及其扩展

扩展欧几里得算法详解 在了解扩欧之前我们应该先了解欧几里得算法 欧几里得算法 这是一个递归求最大公约数(greatest common divisor)的方法 $$ gcd(a, b) = gcd(b, a % b) $$ 可以通过一个类似的简单公式推导而来 好像叫做辗转相减法来着? $$ gcd(

算法学习笔记(3): 倍增与ST算法

倍增 目录倍增查找 洛谷P2249重点变式练习快速幂ST表扩展 - 运算扩展 - 区间变式答案倍增更多的用法优化矩形查询优化建图优化 DP作者有话说 倍增,字面意思即”成倍增长“ 他与二分十分类似,都是基于”2“的划分思想 那么具体是怎么样,我们以一个例子来看 查找 洛谷P2249 依据题面,我们知

算法学习笔记(4): 并查集及其优化

# 并查集 并查集,Disjoint-Set,或者通俗一点,叫做MergeFind-Set,是一种可以动态维护若干个不重叠的集合,并支持集合之间的合并与查询的数据结构。 集体来说,并查集支持下列两个操作: - Find,查询元素所属集合 - Merge,将两个元素所属集合合并 一般来说,为了具体实现