中国剩余定理

中国剩余定理

前置芝士

在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。

定义

中国剩余定理是一种用来求解🐖如

\[\begin{cases}x\equiv a_1~(mod~m_1)\\x\equiv a_2~(mod~m_2)\\......\\x\equiv a_k~(mod~m_k)\end{cases} \]

形式的同余方程组的定理,其中,\(m_1,m_2,...,m_k\)两两互质的整数,我们的目的,是找出\(x\)最小非负整数解。

推导过程

\(M=\prod\limits_{i=1}^km_i,M_i=\frac{M}{m_i},M_it_i\equiv 1~(mod~m_i)\) ,其中 \(1\le i\le k,t_i\)\(M_i\) 在模 \(m_i\) 意义下的逆元(不会的下面会介绍扩展欧几里得的方法)。

所以我们可以构造出来一个解 \(x=\sum\limits_{i=1}^ka_iM_it_i\)

由此,任意解 \(x_0\) 即为 \(x+k*M\)最小整数解 \(x_{min}=x_0\%M\)

证明:

易证,在第 \(i\) 个同余方程中,对于 \(\forall~j\in [i,k]\) ,当 \(i\not=j\) 时,有

\[a_jM_jt_j\equiv 0~(mod~m_i) \]

因为 \(M_k\) 里面是有 \(m_i\) 的,而当 \(i=j\) 时,有

\[a_iM_it_i\equiv a_i~(mod~m_i) \]

因此,\(\sum\limits_{i=1}^ka_iM_it_i\equiv a_i~(mod~m_i)\) ,满足题意

证毕

逆元( \(ex\) 欧几里得)

扩展欧几里得用于在已知\(a,b\)的情况下,求解出一组解 \((x,y)\),使之满足 \(ax+by=gcd(a,b)=d\) 。此处,我们使用扩展欧几里得求逆元。

标准思路:

\[ax+by=gcd(a,b)\\gcd(a,b)=gcd(b,a\%b) \]

下式代入上式,并设

\[bx'+a\%b*y'=gcd(a,a\%b) \]

又因为

\[a\%b=a-\lfloor\frac ab\rfloor*b \]

所以

\[a*y'+b*(x'-\lfloor\frac ab\rfloor*y')=gcd(a,b) \]

故原方程中

\[x=y',y=x'-\lfloor\frac ab\rfloor*y' \]

此外,在开篇的方程组中,我们要求的是\(M_i\)在模\(m_i\)意义下的逆元,而之前我们设\(M_i=\frac M{m_i}\),因此\(M_i\)\(m_i\)互质,\(gcd(M_i,m_i)=1\)。在这里我们把\(M_i\)当做\(a\),把\(m_i\)当做\(b\),即\(ax+by=1\),移项得\(ax=1-by\),显而易见\(ax\equiv 1~(mod~b)\),因此 \(x\) 即我们要求的逆元。

模板题

题目

给定\(n\)组非负整数 \(a_i,b_i\) ,求解关于 \(x\) 的方程组的最小非负整数解。

\[\begin{cases}x\equiv b_1(\mod~a_1)\\x\equiv b_2(\mod~a_2)\\...\\x\equiv b_n(\mod~a_n)\end{cases} \]

其中 \(a_i,a_j\) 互质(去掉这句话就是扩展版

代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define ll long long
ll n,a[16],b[16],Mi[16],M=1,ans;

inline int read(){
    int io=0;char in=getchar();
    while(in<'0'||in>'9')in=getchar();
    while(in>='0'&&in<='9')io=(io<<3)+(io<<1)+(in^'0'),in=getchar();
    return io;
}

void exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0) {x=1;y=0;return ;}
	exgcd(b,a%b,x,y);
	int z=x;x=y,y=z-y*(a/b);
}

int main(){
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		b[i]=read();
		M*=a[i];
	}
	for(int i=1;i<=n;i++)
	{
		Mi[i]=M/a[i];
		ll x=0,y=0;
		exgcd(Mi[i],a[i],x,y);//逆元  
		ans+=b[i]*Mi[i]*(x<0?x+a[i]:x); 
	}
	printf("%lld\n",ans%M);
	return 0;
}
posted @ 2020-07-25 16:56  jasony_sam  阅读(224)  评论(0编辑  收藏  举报