中国剩余定理

预备知识:

(1)(a+b)\%c = (a\%c+b\%c)\%c

(2)存在两个数:m_1m_2,设dgcd(m_1,m_2)(m_1m_2的最大公约数),则有:

         \frac{m_1m_2}{d} = [m_1,m_2]([m_1,m_2]m_1m_2的最小公倍数)

(3)如果一个数x可能的数值为x+kx_0,那么这个数的最小正整数解可以表示为(x\%x_0+x_0)\%x_0

(4)在计算机中,负数模上负数的结果仍然是负数:

        如-5\quad mod\quad 3 = -2

整数解(算法原理不是这个):

已知m_1,m_2,m_3,...,m_k两两互质有解的前提条件),求解下列方程组:

M = m_1m_2...m_k

M_i = \frac{M}{m_i}m_im_1,m_2,...,m_k中的任意一个m_i),M_i就是除m_i外所有项的乘积

因为 m_1,m_2,m_3,...,m_k两两互质,所以M_im_i互质,那么运用逆元的概念:

必定存在一个x满足M_ix \equiv 1(mod\quad m_i),这个x就是M_im_i意义下的乘法逆元,一般记作:{M_i}^{-1}

公式解(不要问公式怎么推的):x = a_1M_1{M_1}^{-1}+a_2M_2{M_2}^{-1}+...+a_kM_k{M_k}^{-1}

实例:

对于第一个方程:x\equiv a_1(mod \quad m_1),将这个方程翻译一下,就是:

x\quad mod\quad m_1 =a_1\quad mod \quad m_1,将x的公式解代入到方程中,那么就是:

(a_1M_1{M_1}^{-1}+a_2M_2{M_2}^{-1}+...+a_kM_k{M_k}^{-1})mod\quad m_1 = (a_1M_1{M_1}^{-1}mod\quad m_1+a_2M_2{M_2}^{-1}mod\quad m_1 + ...+a_kM_k{M_k}^{-1}mod\quad m_1)mod\quad m_1

因为根据逆元的定义:M_1{M_1}^{-1}mod\quad m_1 = 1mod \quad m_1 = 1,将其代入至上面的公式中:

(a_1M_1{M_1}^{-1}mod\quad m_1+a_2M_2{M_2}^{-1}mod\quad m_1 + ...+a_kM_k{M_k}^{-1}mod\quad m_1)mod\quad m_1

得到:

(a_1+a_2M_2{M_2}^{-1}mod\quad m_1 + ...+a_kM_k{M_k}^{-1}mod\quad m_1)mod\quad m_1

对于括号中的第二项:因为M_2是除m_2外的所有数的乘积,所以M_2mod\quad m_1一定是0,对于其它项,也是一样的道理,因此计算过后上式就变为:

a_1 mod\quad m_1这是xmod\quad m_1化简之后的式子,发现正好方程组的第一个方程成立

再以同样的方式将x的公式解代入至方程组的其他方程中,发现均符合,所以x的公式解就是

x = a_1M_1{M_1}^{-1} + a_2M_2{M_2}^{-1} +...+a_kM_k{M_k}^{-1}

如何求逆元?

(之前学的快速幂求逆元要求模上的数必须是质数,才可以用快速幂,但此时模上的数不一定是质数,因此需要用裴蜀定理扩展欧几里得算法):

{M_i}^{-1}看成x,有公式:M_1x\equiv 1(mod\quad m_1),将公式进行变形得到:M_1x = m_1y+1

(其中的y是任意一个整数,当M_1m_1互质时必定存在),设y^{'} = -y,得到:

M_1x+m_1y^{'} =1,因为M_1m_1互质,所以gcd(M_1,m_1) = 1,所以M_1x+m_1y^{'} = gcd(M_1,m_1),这就是裴蜀定理的公式,采用扩展欧几里得算法进行求解

算法原理:

假设此时方程组中只有两个方程

x \equiv a_1(mod\quad m_1)

x \equiv a_2(mod\quad m_2)

将他们变形为:

x = k_1m_1+a_1

x = k_2m_2+a_2

可得k_1m_1+a_1 = k_2m_2+a_2,等式变换一下,得到k_1m_1+k_2(-m_2) = a_2-a_1

发现这是线性同余方程的形式(线性同余方程:http://t.csdnimg.cn/1itN4http://t.csdnimg.cn/1itN4http://t.csdnimg.cn/1itN4

因此只要(m_1,-m_2)|a_2-a_1,那么就一定可以求出一组k_1,k_2,使得等式成立

求出特解后,可以由此推算出k_1的所有解的形式k_1+k\frac{m_2}{d}

k_2的所有解的形式k_2+k\frac{m_1}{d}

(其中:dm_1-m_2的最大公约数;k是任意一个整数)

那么x的所有解就可以表示为:(用k_1表示)

x = (k_1+k\frac{m_2}{d})m_1+a_1 = k_1m_1+a_1+k\frac{m_2m_1}{d}

(用k_2表示)

x = (k_2+k\frac{m_1}{d})m_2+a_2 = k_2m_2+a_2+k\frac{m_2m_1}{d}

因为k_1m_1+a_1 = k_2m_2+a_2,所以两种表示形式下,x是相等的。

因为k_1的所有解为k_1+k\frac{m_1m_2}{d},我们想要取得k_1的最小正整数解,用这样的方式:

ll t = abs(m_2/d);
k_1 = (k_1%t + t)%t;

 就可以得到k_1的最小正整数解。

那么x = k_1m_1+a_1,这个x就是开始方程组的解。

根据x的所有解的形式,得到:x = (k_1m_1+a_1)+k\frac{m_1m_2}{d}

发现得到的等式的形式和一开始方程组中的方程形式类似,因此我们将第一个方程中的a_1更新为k_1m_1+a_1m_1更新为abs(\frac{m_1m_2}{d})

 这样的话,我们就将一开始方程组中的两个方程合并为了一个方程,并且用这个合并之后的方程替代了原本方程组中的第一个方程,如果方程组中还有第三个方程,就用这个新的第一个方程和第三个方程合并,再次得到一个新的方程,接下来就以此类推,直到方程组中没有剩下的方程了。

那么最后得到的x = k_1m_1+a_1,就一定是方程组中所有方程的解,但不一定是最小的

所以还要x的最小正整数解

所以将k_1 = 0,那么x = a_1,所以求x的最小正整数解就是求a_1的最小正整数解,根据预备知识(3)所以x的最小正整数解可以表示为:

(a_1\%m_1+m_1)\%m_1

step1:

在循环外读入m_1a_1,因为除了第一次循环m_1a_1会作为原始方程组中的第一个方程中的数,之后的循环中,m_1a_1是作为之前所有方程的合并方程的数

m_1a_1起到了记录合并方程的作用

step2:

开始循环

读入新的方程中的数m_2a_2,结合第一个方程,得到一个线性同余方程(http://t.csdnimg.cn/1itN4),利用扩展欧几里得算法求出m_1-m_2的最大公约数d

如果a_2-a_1不能整除d,说明这个线性同余方程无解,那么读入的两个方程就没有共同的解,推出循环

如果a_1-a_1能够整除d,说明这个线性同余方程一定有解。

(但是因为扩展欧几里得算法求出的k_1满足的是k_1m_1+k_2(-m_2) = d,所以要想得到满足k_1m_1+k_2(-m_2) = a_2-a_1,要将求出的k_1更新为k_1*{\frac{a_2-a_1}{d}}

step3:

 根据上一个板块 算法原理 我们知道了k_1的所有解的形式为k_1+k\frac{m_2}{d},因为题目要求的是x的最小整数解,所以要求出k_1所有解中的最小正整数解

(k_1\%t+t)\%t注意:t\frac{m_2}{d}的绝对值形式,如果t取到负值的话,k_1是无法取得最小正整数

step4:

 将更新之后的k_1代入至第一个方程中(或者第二个方程),x = k_1m_1+a_1得到的x就是初始两个方程的共同解

step5:

a_1更新为k_1m_1+a_1,将m_1更新为abs(\frac{m_2m_1}{d}),这样就将方程组中的第一个方程的数更新为了原来的第一个方程和第二个方程的合并方程的数。

继续循环下去,不断将m_1a_1更新为循环读入的所有方程的合并方程中的数。

step6:

直到循环结束,那么此时m_1a_1就是方程组中所有方程的合并方程的中的两个数

那么最后一次循环中的x = k_1m_1+a_1就是所有方程合并方程的解,自然就是整个方程组的解

step7:

为了确保xx所有解中的最小正整数解,将k_1 = 0,那么x = a_1,所以x的最小正整数解就是求a_1的最小正整数解,根据预备知识(3)所以x的最小正整数解可以表示为:

(a_1\%m_1+m_1)\%m_1

题目如下:

给定 2n 个整数 a1,a2,…,an和 m1,m2,…,mn,求一个最小的非负整数 x,满足 ∀i∈[1,n],x≡ai(mod mi)

输入格式

第 1 行包含整数 n。

第 2…n+1 行:每 i+1 行包含两个整数 mi 和 ai,数之间用空格隔开。

输出格式

输出最小非负整数 x,如果 x 不存在,则输出 −1。

数据范围

1\leqslant a_i \leqslant 2^{31}-1
0≤mi<ai
1≤n≤25
所有 mi 的最小公倍数在 64 位有符号整数范围内。

代码如下:

#include<iostream>
#include<cstring>

using namespace std;

typedef long long ll;

int n;

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    ll d = exgcd(b,a%b,x,y);
    ll tmp = x;
    x =y;
    y = tmp-a/b*y;
    return d;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    cin >> n;
    
    ll a_1,m_1;
    cin >>m_1 >> a_1;
    
    ll x = 0;
    
    for (int i = 0;i<n-1;++i)
    {
        ll m_2,a_2;
        cin >> m_2 >> a_2;
        
        ll k_1,k_2;
        ll d = exgcd(m_1,-m_2,k_1,k_2);
        
        if ((a_2-a_1)%d != 0)
        {
            x = -1;
            break;
        }
        
        k_1 = k_1*(a_2-a_1)/d;
        
        ll t = abs(m_2/d);
        k_1 = (k_1%t+t)%t;
        
        x = k_1*m_1+a_1;
        
        a_1 = k_1*m_1+a_1;
        m_1 = abs(m_1/d*m_2);
    }
    if (x !=-1)
        x = (a_1%m_1+m_1)%m_1;
        
    cout << x;
    return 0;
}

(1)为什么需要 ll t = abs(m_2/d) 这一步 

因为题目要求的是求x的最小正整数解,而x的解的形式为k_1m_1+a_1,所以要让k_1取得尽可能小,也就是取得k_1的最小正整数解,而k_1的所有解的形式为k_1+k\frac{m_2}{d},那么就通过(k_1\%t+t)\%t(其中:t = abs(m_2/d))的方式,得到k_1的最小正整数解。

对于这个求最小正整数解的公式,简单模拟一下就明白过程和为什么需要t = abs(m_2/d)

需要用到预备知识(4)

(2)为什么m_1要更新为abs(\frac{m_1m_2}{d})要加abs

不会,背下来就完了。

但可以明确的是有没有 abs 对结果不会产生影响

因为合并之后的方程的形式为:x = k_1m_1+a_1+k\frac{m_1m_1}{d},我们将a_1更新为k_1m_1+a_1,将m_1更新为abs(m_1/d*m_2)

如果\frac{m_1m_2}{d}>0,那么这个abs加不加都无所谓

如果\frac{m_1m_2}{d}<0,那么加了这个abs后,m_1更新之后的值就会变为-\frac{m_2m_1}{d},和不加abs的更新值\frac{m_1m_2}{d},相差了2\frac{m_1m_2}{d},而合并方程中的k是一个任意整数,因此k可以适应m_1更新之后的值的不同。

(3)为什么最后要x = (a_1\%m_1+m_1)\%m_1

因为最后x的解的形式为x = k_1m_1+a_1,像要求得x的最小正整数解,就要让k_1 = 0,那么x =a_1,所以求x的最小正整数解就是求a_1的最小正整数解,而a_1的所有可能的解为a_1+km_1

所以a_1的最小正整数解为(a_1\%m_1+m_1)\%m_1

(4) 为什么m_1 = abs(m_1/d*m_2),不能是m_1  = abs((m_2*m_1)/d)

因为 m_2*m_1 的数可能很大,会爆掉 long long ,如果先除的话,就不会出现爆 long long 的情况。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值