当前位置: 首页 > news >正文

【算法学习笔记】37:扩展中国剩余定理(EXCRT)求解任意线性同余方程组

中国剩余定理仅适用于模数 m 1 . . . m n m_1...m_n m1...mn两两互质的情况,如果没有这个限制条件则不能使用。

下面进行一般情况的解法推导,注意虽然叫扩展中国剩余定理(EXCRT),实际上只是扩宽了中国剩余定理(CRT)的求解限制,解法和原理上和CRT完全没啥关系。

1 推导过程

对于一般的线性同余方程组:
x   m o d   m 1 ≡ a 1 x   m o d   m 2 ≡ a 2 . . . x   m o d   m n ≡ a n x~mod~m_1 \equiv a_1 \\ x~mod~m_2 \equiv a_2 \\ ... \\ x~mod~m_n \equiv a_n x mod m1a1x mod m2a2...x mod mnan

1.1 对于任意两个方程求得一组解 k 1 k_1 k1 k 2 k_2 k2

先考虑其中两个方程组成的方程组:
x   m o d   m 1 ≡ a 1 x   m o d   m 2 ≡ a 2 x~mod~m_1 \equiv a_1 \\ x~mod~m_2 \equiv a_2 x mod m1a1x mod m2a2

说明存在 k 1 k_1 k1 k 2 k_2 k2分别是 x x x除以 m 1 m_1 m1 m 2 m_2 m2的解,并留下余数是 a 1 a_1 a1 a 2 a_2 a2,也即:
x = k 1 ⋅ m 1 + a 1 x = k 2 ⋅ m 2 + a 2 x = k_1 \cdot m_1 + a_1 \\ x = k_2 \cdot m_2 + a_2 x=k1m1+a1x=k2m2+a2

从而可以用 x x x把两式关联起来:
k 1 ⋅ m 1 + a 1 = k 2 ⋅ m 2 + a 2 k_1 \cdot m_1 + a_1 = k_2 \cdot m_2 + a_2 k1m1+a1=k2m2+a2

整理即得一个不定方程:
k 1 ⋅ m 1 − k 2 ⋅ m 2 = a 2 − a 1 k_1 \cdot m_1 - k_2 \cdot m_2 = a_2 - a_1 k1m1k2m2=a2a1

其中 m 1 m_1 m1 m 2 m_2 m2 a 2 a_2 a2 a 1 a_1 a1都是给定的,只要求出 k 1 k_1 k1 k 2 k_2 k2就能算出 x x x了,只要用扩展欧几里得算法就能计算 k 1 k_1 k1 − k 2 -k_2 k2,仅当 a 2 − a 1 a_2-a_1 a2a1能被 m 1 m_1 m1 m 2 m_2 m2的最大公约数整除时候有解,即 g c d ( m 1 , m 2 )   ∣   a 2 − a 1 gcd(m_1, m_2)~|~a_2 - a_1 gcd(m1,m2)  a2a1

1.2 从 k 1 k_1 k1 k 2 k_2 k2得到 x x x的所有解的表示形式

从前面的推导可以得出:
x = k 1 ⋅ m 1 + a 1 x = k 2 ⋅ m 2 + a 2 x = k_1 \cdot m_1 + a_1 \\ x = k_2 \cdot m_2 + a_2 x=k1m1+a1x=k2m2+a2

只要得到了一组 k 1 k_1 k1 k 2 k_2 k2,已经满足了 k 1 ⋅ m 1 − k 2 ⋅ m 2 = a 2 − a 1 k_1 \cdot m_1 - k_2 \cdot m_2 = a_2 - a_1 k1m1k2m2=a2a1,对于最大公约数 d = g c d ( m 1 , m 2 ) d = gcd(m_1, m_2) d=gcd(m1,m2),只要找任何一个倍数 k k k,让 k 1 k_1 k1加上 k ⋅ m 2 d k \cdot \frac{m_2}{d} kdm2,让 k 2 k_2 k2加上 k ⋅ m 1 d k \cdot \frac{m_1}{d} kdm1,该等式还是成立的。

所以引入整数 k k k来表示 x x x的所有解,应该形如(只看第一个式子的话):
x = ( k 1 + k m 2 d ) m 1 + a 1 x = (k_1 + k \frac{m_2}{d})m_1 + a_1 x=(k1+kdm2)m1+a1

整理一下,得到:
x = m 1 k 1 + a 1 + k m 1 m 2 d x = m_1k_1 + a_1 + k\frac{m_1m_2}{d} x=m1k1+a1+kdm1m2

其中 d = g c d ( m 1 , m 2 ) d = gcd(m_1, m_2) d=gcd(m1,m2),所以 m 1 m 2 d \frac{m_1m_2}{d} dm1m2也即 l c m ( m 1 , m 2 ) lcm(m_1, m_2) lcm(m1,m2),因此:
x = m 1 k 1 + a 1 + k ⋅ l c m ( m 1 , m 2 ) x = m_1k_1 + a_1 + k \cdot lcm(m_1, m_2) x=m1k1+a1+klcm(m1,m2)

1.3 任意两个不定方程的合并

其中 m 1 k 1 + a 1 m_1k_1 + a_1 m1k1+a1这一项在求出 k 1 k_1 k1之后就是固定的了,记为 A A A,后面的 l c m ( m 1 , m 2 ) lcm(m_1, m_2) lcm(m1,m2)记为 M M M,则 x x x的所有解的形式就可以表示为:
x = k ⋅ M + A x = k \cdot M + A x=kM+A

这个方程不仅和前面的不定方程形式一样,而且其中 M M M A A A都是确定的, k k k是变化的。所以总是可以通过这样的方式把两个不定方程合并成一个,也即对于前两个方程
x = k 1 ⋅ m 1 + a 1 x = k 2 ⋅ m 2 + a 2 x = k_1 \cdot m_1 + a_1 \\ x = k_2 \cdot m_2 + a_2 x=k1m1+a1x=k2m2+a2

可以合并为 x = k M + A x = k M + A x=kM+A,其中 M = l c m ( m 1 , m 2 ) M = lcm(m_1, m_2) M=lcm(m1,m2) A = m 1 k 1 + a 1 A = m_1k_1 + a_1 A=m1k1+a1,然后可以拿着新方程和第三个方程继续合并,即合并:
x = k ⋅ M + A x = k 3 ⋅ m 3 + a 3 x = k \cdot M + A \\ x = k_3 \cdot m_3 + a_3 x=kM+Ax=k3m3+a3

一共有 n n n个方程,只要这样合并 n − 1 n-1 n1次就只剩一个方程 x = k ⋅ M + A x = k \cdot M + A x=kM+A了,这时要找 x x x的最小正整数取值就很简单了,一定是 A A A M M M的正的余数。

2 例题:AcWing 204. 表达整数的奇怪方式

注意这题里 a a a m m m的含义和前面的推导是相反的。

这题里限制了方程的数量 n n n是不超过25个的,因为每次合并两个方程,得到的新方程的 M M M都是前两个方程里 m i m_i mi m j m_j mj的最小公倍数,所以最后的 M M M就是 m 1 . . . m n m_1...m_n m1...mn的最小公倍数,如果方程太多了这个最小公倍数就太大了,可能超出整数范围,所以数据里做了这样的限制。

为了防止得到的新的方程里 k k k越来越大爆掉,每次都取一个最小的正整数 k k k

小技巧:要取任何一个整数p对于模q的数学模(最小正整数),只要(p % q + q) % q即可

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

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

int main() {
    int n; cin >> n;
    
    // 读取第一组方程
    LL m1, a1; cin >> m1 >> a1;
    for (int i = 1; i < n; i ++ ) {
        // 每次合并完读取下一组方程
        LL m2, a2; cin >> m2 >> a2;
        // 尝试合并两个方程
        // x = k1 m1 + a1
        // x = k2 m2 + a2
        // k1 m1 - k2 m2 = a2 - a1
        // 用exgcd计算k1和-k2(丢弃)
        LL k1, k2;
        LL d = exgcd(m1, m2, k1, k2);
        // 如果有解,d必须是a2 - a1的倍数
        if ((a2 - a1) % d) {
            puts("-1");
            return 0;
        }
        // 把d变成a2 - a1时候,k1也跟着倍增
        k1 *= (a2 - a1) / d;
        // 由于k1可以是任意的k1 + k * (m2 / d)
        // 这里为了防止太大,每次把k1变成满足条件的最小正值
        LL t = m2 / d;
        k1 = (k1 % t + t) % t;
        // 接下来有了k1,合并好的新方程是
        // x = k1 M + A,其中
        // A = m1 k1 + a1,M = lca(m2, m1)
        // 这里M就是下一轮的m1,A就是下一轮的a1
        a1 = m1 * k1 + a1;
        m1 = m1 * m2 / d;
    }
    
    // 最后的解是上一轮的A = m1 k1 + a1,放在了a1里
    // 为了调整k1让A是最小正整数,即是对m1取模时的最小正数
    cout << (a1 % m1 + m1) % m1 << endl;
    
    return 0;
}

相关文章:

  • 【微服务管理】注册中心:分布式系统的基石
  • python每日一练
  • 【模块化拆解与多视角信息3】教育背景:学历通胀时代的生存法则
  • JMeter使用
  • css解决边框四个角有颜色
  • 关于数据清洗和数据处理实践学习笔记
  • 任意文件读取 + java逆向 -- File_download sqctf WP
  • 【中级软件设计师】前趋图 (附软考真题)
  • HJ16 购物单
  • 【Linux生成SSH秘钥实现远程连接】Linux生成SSH秘钥对与修改服务配置文件实现无密码远程连接
  • PyCharm 开发工具 修改背景颜色
  • VMware vCenter Server 安全漏洞升级方案一则
  • 基于 SSM 高校二手交易平台
  • 如何在 Java 中对 PDF 文件进行数字签名(教程)
  • 打造现代数据基础架构:MinIO对象存储完全指南
  • 如何快速部署基于Docker 的 OBDIAG 开发环境
  • 初识大模型
  • OpenAI 焕新力作:ChatGPT 开启“记忆长廊”,对话皆成专属印记
  • 自然语言处理spaCy
  • 多模态融合学习(九)——PIAFusion 武汉大学马佳义团队(一)
  • 五角大楼正在“全面崩溃”?白宫被指已在物色新国防部长
  • 三部门:对不裁员少裁员的参保企业实施稳岗返还政策至今年底
  • 上海之旅相册②俄罗斯Chaika:客居六年,致上海的情书
  • 美股再遭重挫,标普500指数11个板块全线溃败
  • 复旦大学附属中山医院也有儿科了,门诊将于下月底开业
  • 30小时已过,俄罗斯复活节停火不再延长