《孙子算经》中有这么个问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?我们可以用如下的同余方程组表示这个问题:
显然我们直接找这个x是十分困难的,但是我们可以很容易找到,使得他们满足:
那么我们看看是不是有可能满足上一个不等式呢?显然有点憨啊2333。不过呢,我们来一个式子一个式子地看,当我们要求时,我们可以推出并且。我们在考察了三个式子之后,发现实际上,我们会得到要让成为答案,除了满足上述不等式之外,还需要让是5的倍数和7的倍数,而由于5和7互质,因此要求是35的倍数,要求是21的倍数,要求是15的倍数。于是我们将重新表示为如下:
带入上述同余方程后我们得到:
由于,一次上述三个方程必定有解,并且可以通过扩展欧几里得的方式求出。我们通过扩展欧几里得解如下三个方程:
有没有发现这边就是在求解逆元。并且由于模数之间两两互质,我们知道,的系数比与所在方程的模数互质(如35和3),于是在这种条件下,我们有很多方法可以使用,最简单的莫过于费马小定理(注意数据溢出)。
并且有如下关系:
解得,进一步,我们得到
于是我们可以解出。由于3、5、7互质,因此,任何一个特解x加上或者减去3*5*7(最大公倍数)均不会影响答案。
中国剩余定理:
现在我们抽象化上述过程:
设有一组待解方程为:
为了构造出答案,我们要求:
设(即为所有模数的乘积),为了使得我们构造出答案,因此需要满足倍数性质,于是方程可以转换为求解:
由于与互质,因此上述的所有式子均有解,于是我们可以解如下方程组
于是我们构造出来的一个特解就可以表示为,由于模数两两互质,因此对与x加上或者减去p都不影响方程结果。
来看一道例题吧~
洛谷 P1495 【模板】中国剩余定理(CRT)/曹冲养猪
提交10.44k
通过4.31k
时间限制1.00s
内存限制125.00MB
题目描述
自从曹冲搞定了大象以后,曹操就开始捉摸让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲满不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。举个例子,假如有 16 头母猪,如果建了 3 个猪圈,剩下 1 头猪就没有地方安家了。如果建造了 5 个猪圈,但是仍然有 1 头猪没有地方去,然后如果建造了 7 个猪圈,还有 2 头没有地方去。你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?
输入格式
第一行包含一个整数 n—建立猪圈的次数,接下来 n 行,每行两个整数 a_i,b_i, 表示建立了 a_i 个猪圈,有 b_i头猪没有去处。你可以假定 a_i,a_j互质。
输出格式
输出包含一个正整数,即为曹冲至少养母猪的数目。
输入输出样例
输入 #1复制
3
3 1
5 1
7 2
输出 #1复制
16
思路:本题的数据范围很大,使用费马小定理求逆元可能需要处理高精度,因此我们这里选择使用扩展欧几里得。
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
typedef long long ll;
ll m[N],a[N];
// gcd(a,b) = gcd(b,a%b)
// a * x1 + b * y1 = b * x + ( a - a/b*b ) * y
// a * x1 + b * y1 = a * y + ( x - a/b*y ) b
// x1 = y , y1 = x - a/b*y
ll extgcd(ll a,ll b,ll &x,ll &y){
if( ! b ){
x = 1;
y = 0;
return a;
}
ll temp = extgcd(b,a%b,x,y);
ll x1,y1;
x1 = y;
y1 = x - a/b*y;
x = x1;
y = y1;
return temp;
}
ll inv(ll a,ll b){ // 求a在模b意义下得逆元
ll x,y,gcd; //本题中 a b 必互质 但是为了算法的通用性 此处给出最小整数解的公式
gcd = extgcd(a,b,x,y);
b /= gcd;
return ( (x % b) + b ) % b;
}
ll CRT(int n){
ll p = 1, x = 0, invQ;
int i;
for( i = 1;i <= n; ++ i){
p *= m[i];
}
for( i = 1;i <= n; ++ i){
invQ = p / m[i];
x += ( invQ * a[i] * inv( invQ, m[i]) ) % p;
}
return x % p;
}
int main(){
int i, n;
ll x, p;
cin >> n;
for( i = 1;i <= n; ++ i){
cin >> m[i] >> a[i];
}
cout<< CRT(n);
return 0;
}
扩展中国剩余定理
当然在实际问题中,我们可能遇到的是模不为质数,甚至都不满足互质的情形,此时就需要请出我们的扩展中国剩余定理了。不过扩展中国剩余定理和中国剩余定理没有半毛钱关系=w=,而是通过n次扩展欧几里得求解答案:
有一组待解同余方程组为:
我们假设在考虑了前个同余方程后,得到的特解为,且设,那么通解可以表示为,其中M为正整数,此时我们将通解带入第个方程,就可以得到,移项得,这个方程的有解条件为。通过扩展欧几里得我们将同余方程不断合并,得到新的通解表达式,直到处理完所有方程,算法结束。
在初始化的时候,我们就可以使,然后逐步考虑每一个方程。
洛谷 P4777 【模板】扩展中国剩余定理(EXCRT)
提交15.96k
通过5.62k
时间限制1.00s
内存限制500.00MB
题目描述
给定 nn 组非负整数 a_i, b_iai,bi ,求解关于 xx 的方程组的最小非负整数解。
输入格式
输入第一行包含整数 nn。
接下来 nn 行,每行两个非负整数 a_i, b_iai,bi。
输出格式
输出一行,为满足条件的最小非负整数 xx。
输入输出样例
输入 #1复制
3 11 6 25 9 33 17
输出 #1复制
809
思路:模板题,需要注意的是本题数据可能会在乘法的过程中爆long long 型的精度范围,因此可以写一个龟速乘(快速幂变快速乘,一样的二进制分解思想)。同时注意输入的数据也会超过int的数据类型,使用快读需注意。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MaxN = 100010;
ll a[MaxN],m[MaxN];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
ll quickMul(ll a,ll b,ll p){
ll res = 0;
while( b ){
if(b & 1){
res = (res + a) % p;
}
a = ( a + a ) % p;
b >>= 1;
}
return res;
}
ll extgcd(ll a,ll b,ll &x,ll &y){
if ( ! b ){
x = 1;
y = 0;
return a;
}
ll temp = extgcd(b,a%b,x,y);
ll x1,y1;
x1 = y;
y1 = x - a/b * y;
x = x1;
y = y1;
return temp;
}
ll solve(int n){
int i;
ll M = m[0],x = a[0],c,gcd,t,y;
for(i = 1; i < n; ++ i){
c = (( a[i] - x ) % m[i] + m[i] ) %m[i] ;//右边的数 通过取模 对齐到正数
gcd = extgcd(M, m[i], t, y);
if( c % gcd != 0) return -1; //无解
m[i] /= gcd;
// t = ( (t % m[i] + m[i]) % m[i] ) * (c / gcd);// 求t的最小正正数解
t = quickMul(t, c / gcd, m[i]);
x += t * M;
M *= m[i] ;
x = (x % M + M) % M;
}
return (x % M + M) % M;
}
int main(){
int n, i;
n = read();
for( i = 0; i < n ; ++ i){
scanf("%lld%lld",m+i,a+i);
}
printf("%lld\n",solve(n));
return 0;
}