浅谈扩展中国剩余定理

洛谷p4777

前言

在洛谷上,这是一个连模板都是省选/NOI-的题目,我也钻研了好几天,也许还没有达到通透的境界,但我还是想写一些我在学习过程中的一些感想。

用途

这个定理,其实不能说是一个定理,我觉得更像一个算法。
给出如下同余方程组,让你求方程的最小正数解:
x ≡ b 1 x≡b_1 xb1(mod a 1 ) a_1) a1)
x ≡ b 2 x≡b_2 xb2(mod a 2 ) a_2) a2)
……
x ≡ b i x≡b_i xbi(mod a i ) a_i) ai)

算法

我们先从简单的着手,一步一步来。先看前两个方程。
根据第一个方程,我们可以设 x = a 1 ∗ x 1 + b 1 x=a_1*x_1+b_1 x=a1x1+b1。同理,根据第二个方程,我们可以设 x = a 2 ∗ ( − x 2 ) + b 2 x=a_2*(-x_2)+b_2 x=a2(x2)+b2。( x 2 x_2 x2前面加个负号是为了方便下面的移项)
因为x是相等的,所以 a 1 ∗ x 1 + b 1 = a 2 ∗ ( − x 2 ) + b 2 a_1*x_1+b_1=a_2*(-x_2)+b_2 a1x1+b1=a2(x2)+b2
移项得: a 1 ∗ x 1 + a 2 ∗ x 2 = b 2 − b 1 a_1*x_1+a_2*x_2=b_2-b_1 a1x1+a2x2=b2b1
a 1 ∗ x 1 ′ + a 2 ∗ x 2 ′ = g c d ( a 1 , a 2 ) a_1*x_1'+a_2*x_2'=gcd(a_1,a_2) a1x1+a2x2=gcd(a1,a2)的解是可以通过扩展欧几里得求出来的
如果 b 2 − b 1 b_2-b_1 b2b1不是 g c d ( a 1 , a 2 ) gcd(a_1,a_2) gcd(a1,a2)的倍数,那么无解。
然后有 a 1 ∗ x 1 ′ ∗ ( b 2 − b 1 ) / g c d ( a 1 , a 2 ) + a 2 ∗ x 2 ′ ∗ ( b 2 − b 1 ) / g c d ( a 1 , a 2 ) = b 2 − b 1 a_1*x_1'*(b_2-b_1)/gcd(a_1,a_2)+a_2*x_2'*(b_2-b_1)/gcd(a_1,a_2)=b_2-b_1 a1x1(b2b1)/gcd(a1,a2)+a2x2(b2b1)/gcd(a1,a2)=b2b1
x 1 = x 1 ′ ∗ ( b 2 − b 1 ) / g c d ( a 1 , a 2 ) , x 2 = x 2 ′ ∗ ( b 2 − b 1 ) / g c d ( a 1 , a 2 ) x_1=x_1'*(b_2-b_1)/gcd(a_1,a_2),x_2=x_2'*(b_2-b_1)/gcd(a_1,a_2) x1=x1(b2b1)/gcd(a1,a2)x2=x2(b2b1)/gcd(a1,a2)
当然,用扩展欧几里得求出的只是一组解,而我们希望得到的是最小的那一组解,所以要调整一下 x 1 x_1 x1
x 1 x_1 x1变成 ( x 1 % a 2 + a 2 ) % a 2 (x_1\% a_2+a_2)\% a_2 (x1%a2+a2)%a2,这样 x 1 x_1 x1就是最小正数解了。
那么 x = ( a 1 ∗ x 1 + b 1 ) % ( a 1 ∗ a 2 / g c d ( a 1 , a 2 ) ) x=(a_1*x_1+b_1)\% (a_1*a_2/gcd(a_1,a_2)) x=(a1x1+b1)%(a1a2/gcd(a1,a2))
记最终的答案为ans, a 1 ∗ a 2 / g c d ( a 1 , a 2 ) a_1*a_2/gcd(a_1,a_2) a1a2/gcd(a1,a2)为m,那么 a n s ≡ x ans≡x ansx(mod m m m)
这样我们就构造出了一个新的同余方程,来代替这两个方程。

推向一般化,我们现在有这两个同余方程:
a n s ≡ x ans≡x ansx(mod m m m)
a n s ≡ b i ans≡b_i ansbi(mod a i a_i ai)
按照上面的步骤再走一遍。
根据第一个方程,设 a n s = m ∗ x 1 + x ans=m*x_1+x ans=mx1+x。同理,根据第二个方程,设 a n s = a i ∗ ( − x 2 ) + b i ans=a_i*(-x_2)+b_i ans=ai(x2)+bi
那么 m ∗ x 1 + x = a i ∗ ( − x 2 ) + b i m*x_1+x=a_i*(-x_2)+b_i mx1+x=ai(x2)+bi
移项得: m ∗ x 1 + a i ∗ x 2 = b i − x m*x_1+a_i*x_2=b_i-x mx1+aix2=bix
z = b i − x z=b_i-x z=bix,要是z很大或者是个大负数,那将会面临溢出的风险,所以要取模。但对什么取模呢?因为在后面 x 2 x_2 x2的值根本没用到,所以z可以对 a i a_i ai取模,这样只对 x 2 x_2 x2的值有影响,对 x 1 x_1 x1并没有影响。
根据扩展欧几里得求出 m ∗ x 1 ′ + a i ∗ x 2 ′ = g c d ( m , a i ) m*x_1'+a_i*x_2'=gcd(m,a_i) mx1+aix2=gcd(m,ai)的一组解
如果 z z z不是 g c d ( m , a i ) gcd(m,a_i) gcd(m,ai)的倍数,那么无解。
然后有 m ∗ x 1 ′ ∗ z / g c d ( m , a i ) + a i ∗ x 2 ′ ∗ z / g c d ( m , a i ) = b i − x m*x_1'*z/gcd(m,a_i)+a_i*x_2'*z/gcd(m,a_i)=b_i-x mx1z/gcd(m,ai)+aix2z/gcd(m,ai)=bix
那么 x 1 = x 1 ′ ∗ z / g c d ( m , a i ) x_1=x_1'*z/gcd(m,a_i) x1=x1z/gcd(m,ai) x 2 = x 2 ′ ∗ z / g c d ( m , a i ) x_2=x_2'*z/gcd(m,a_i) x2=x2z/gcd(m,ai)(其实 x 2 x_2 x2无所谓,反正后面用不到)
再调整 x 1 x_1 x1,使之变为最小正数解: x 1 = ( x 1 % a i + a i ) % a i x_1=(x_1\% a_i+a_i)\% a_i x1=(x1%ai+ai)%ai
然后构造新的同余方程: x = ( m ∗ x 1 + x ) % ( m / g c d ( m , a i ) ∗ a i ) x=(m*x_1+x)\% (m/gcd(m,a_i)*a_i) x=(mx1+x)%(m/gcd(m,ai)ai) m = m / g c d ( m , a i ) ∗ a i m=m/gcd(m,a_i)*a_i m=m/gcd(m,ai)ai
则新的同余方程组还是 a n s ≡ x ans≡x ansx(mod m m m)(ans只是存在于我们脑袋中的一个量,最终的答案就是x)
由于数据范围非常大,上面的带取模的乘法最好用快速乘,不然可能会溢出。

具体实现看我代码:

#include<iostream>
#include<cmath>
#include<cstdio> 
using namespace std;
int n;
long long a[100005],b[100005];
long long exgcd(long long a,long b,long long &x,long long &y)
{
    if (b==0)
    {
        x=1;
        y=0;
        return a;
    }
    long long gcd=exgcd(b,a%b,x,y);
    int t=x;
    x=y;
    y=t-a/b*y;
    return gcd;
}
long long cheng(long long x,long long y,long long z)//快速乘 
{
    long long t=0;
    x=(x%z+z)%z;//快速乘一定要加这两句话,因为x可能是负数
    y=(y%z+z)%z;
    while (x>0)
    {
        if (x%2==1) t=(t+y)%z;
        y=y*2%z;
        x/=2;
    }
    return t;
}
long long excrt()
{
    long long x=b[1],x1,x2,m=a[1];
    for (int i=2;i<=n;i++)
    {
    	long long z=((b[i]-x)%a[i]+a[i])%a[i];//为避免溢出,要对a[i]取模 
        long long gcd=exgcd(m,a[i],x1,x2);//求m*x1+a[i]*x2=gcd(m,a[i])的一组解,gcd=gcd(m,a[i]) 
		if (z%gcd!=0) return 0;//说明无解 
        x1=cheng(x1,z/gcd,a[i]);//x1=x1*z/gcd,同时调整x1为最小正数解 
        x=cheng(m,x1,m/gcd*a[i])+x;//构造新的同余方程 
        m=m/gcd*a[i];
        x=x%m;//上上行加了x之后还要取一次模
    }
    return x;//x就是最终是答案 
}
int main()
{
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        scanf("%lld %lld",&a[i],&b[i]);
    }
    cout<<excrt();
}

总结

这虽然只是一个模板,但难度却也不小,我学了好久,总是因为一些取模而困惑,希望这一篇文章把细节都写到了。像这种数据规模很大的题目,每一步都要想想能不能取模,对什么取模。

<think>好的,用户想找关于扩展中国剩余定理在MATLAB中的实现或者教程。首先,我需要明确扩展中国剩余定理(Extended Chinese Remainder Theorem, ExCRT)普通CRT的区别。根据之前的引用,CRT解决的是模数两两互质的情况,而ExCRT处理的是模数不互质的情况,可能需要通过合并同余方程逐步求解。 接下来,我需要确认用户的需求:他们可能需要MATLAB代码示例或者相关教程。我需要检查现有的引用资料是否提到了ExCRT的具体算法。引用[4]提到程序中求k1*a1 = gcd(a1,a2) mod a2的解,这可能涉及到扩展欧几里得算法,而这是ExCRT的关键部分。因此,可能需要结合扩展欧几里得算法来实现ExCRT。 然后,我应该考虑如何在MATLAB中实现扩展欧几里得算法,因为这是合并同余方程的基础。需要编写一个函数来计算模逆或者求解贝祖等式。接着,根据ExCRT的步骤,逐步合并每个同余方程,检查是否存在解,并更新当前的解模数。 可能遇到的问题包括如何处理模数不互质的情况,以及在合并方程时如何处理无解的情况。需要确保代码中包括这些情况的处理,比如检查每个步骤中的余数是否满足gcd条件。 另外,用户可能需要教程或代码示例,所以提供一个分步的代码解释会更好。需要确保代码结构清晰,注释详细,并附上示例输入输出,以便用户理解如何使用代码。 最后,根据用户提供的引用,特别是引用[1]中的数论知识引用[4]中的程序步骤,确保代码符合ExCRT的标准实现方法,并正确引用相关资料。同时,生成相关问题,帮助用户进一步学习相关算法应用场景。</think>### 扩展中国剩余定理(ExCRT)的MATLAB实现 扩展中国剩余定理(ExCRT)用于解决模数**不互质**的同余方程组。其核心是通过合并同余方程并求解线性组合,逐步得到最终解。以下是分步实现及代码示例: --- #### **步骤1:扩展欧几里得算法** 实现ExCRT需要先编写扩展欧几里得算法,用于求解方程 $ax + by = \gcd(a,b)$ 的解: ```matlab function [g, x, y] = extended_gcd(a, b) if b == 0 g = a; x = 1; y = 0; else [g, y, x] = extended_gcd(b, mod(a, b)); y = y - floor(a / b) * x; end end ``` --- #### **步骤2:合并同余方程** 合并两个同余方程 $x \equiv a_1 \ (\text{mod} \ m_1)$ $x \equiv a_2 \ (\text{mod} \ m_2)$: ```matlab function [a, m] = merge_equations(a1, m1, a2, m2) [g, p, q] = extended_gcd(m1, m2); if mod(a2 - a1, g) ~= 0 error('无解'); end lcm = m1 / g * m2; % 最小公倍数 a = a1 + (a2 - a1) / g * p * m1; a = mod(a, lcm); m = lcm; end ``` --- #### **步骤3:迭代所有方程** 依次合并所有同余方程: ```matlab function [x, M] = excrt(moduli, remainders) n = length(moduli); a = remainders(1); m = moduli(1); for i = 2:n [a, m] = merge_equations(a, m, remainders(i), moduli(i)); end x = a; M = m; end ``` --- #### **示例调用** ```matlab % 同余方程组: % x ≡ 2 (mod 4) % x ≡ 3 (mod 5) % x ≡ 1 (mod 7) moduli = [4, 5, 7]; remainders = [2, 3, 1]; [x, M] = excrt(moduli, remainders); disp(['解为 x ≡ ', num2str(x), ' mod ', num2str(M)]); % 输出:解为 x ≡ 206 mod 140 ``` --- #### **关键点** 1. **模数不互质的处理**:通过计算$\gcd(m_1, m_2)$,并检查$a_2 - a_1$是否能被其整除,否则无解[^4]。 2. **解的更新**:合并后的解$x$需满足新模数的最小公倍数$m = \text{lcm}(m_1, m_2)$。 3. **数值稳定性**:MATLAB中需注意大数运算时的溢出问题。 --- ### 相关引用 扩展中国剩余定理的实现依赖于扩展欧几里得算法同余方程的逐步合并,这与数论中线性同余的解法一致[^1][^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值