扩展的欧几里得&中国剩余定理
扩展的欧几里得(EXTENDED-EUCLID)
一、假设:
对于给定的整数a和b,它们满足方程:ax+by=d=gcd(a,b),求出整系数x,y
二、推理:
ax+by=gcd(a,b)=gcd(b,a%b)=bx+(a-(int)a/b*b)y=ay+b(x-(a-(int)a/b*y)
三、扩展的欧几里得算法:
1 int extended_gcd(int a, int b, int &x, int &y)
2 {
3 int ret, tmp;
4 if (!b) {
5 x = 1; y = 0; return a;
6 }
7 ret = extended_gcd(b, a % b, x, y);
8 tmp = x;
9 x = y;
10 y = tmp - a / b * y;
11 return ret;
12 }
四、应用:
1、求解模线性方程:ax≡b (mod n)
定理一:设d=gcd(a,n),用扩展欧几里得算法解线性方程 ax'+ny'=d.如果d|b,则方程axºb(mod n)有一个解的值x0=x'(b/d)mod n
定理二:方程axºb(mod n)有解(即存在d|b,其中d=gcd(a,n)),x0是该方程的任意一个解,则该方程对模n恰有d个不同的解,分别为
x(i)=x(0)+i(n/d)(i=1,2,...d-1)
//用扩展欧几里得解模线性方程ax=b (mod n)
bool modularLinearEquation(int a,int b,int n)
{
int x,y,x0,i;
int d=Extended_Euclid(a,n,x,y); //ax=b (mod n) 等价于ax+ny=b
if(b%d)
return false;
x0=x*(b/d)%n;
for(i=1;i<=d;i++)
printf("%d\n",(x0+i*(n/d))%n);
return true;
}
2、求乘法逆元:ax≡1(mod n)
ax≡1(mod n)等价于 ax+ny=1=gcd(a,n),调用extended_gcd(a,n,x,y),并当公约数ret=1(a,n互为质数,ret一定等于1)时,
当x>0时,x即为a的乘法逆元。当x<0时,将x转换为mod n的最小正整数即可:
while(x<0)
x+=n;
注:Java中没有指针,不能向C/C++这样调用;而C/C++可以使用指针或引用传出形参x,这个特性,使C/C++函数能返回多个值,而
Java方法最多只能返回一个值。那么在Java版本的实现中,可以将这里的x定义为一个类变量(static),这样就可以获取x的值了。
中国剩余定理(CRT)
一、 背景介绍:
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),
问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。具体解法分三步:
1、找出三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
2、用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相
加(15*2+21*3+70*2)得到和233。
3、用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23。这个余数23就是符合条件的最小数。
二、 形式化解释:
假设x是那个未知数,而除3,5,7后的余数分别为r1,r2,r3。因此有
x≡r1(mod 3)
x≡r2(mod 5)
x≡r3(mod 7)
第一步:就是找除了r[i]之外的所有余数r[j](j≠i)的乘积(最小公倍数);
第二步:在求n1,n2,n3时又用了一个小技巧,以n1为例,并非从5和7的公倍数中直接找一个除以3余2的数,而是先找一个除以3余1的数,再乘以2。
(定理:如果 a%b=c,那么 (a*k)%b=kc (k为大于零的整数))
第三步:n1+n2+n3只是问题的一个解,并不是最小的解,故最小解应是该解的等价类。
注:中国剩余定理的思想在于先找到一个满足条件的数,不管是不是最小的,如果不是最小的就不断减公倍数,中国剩余定理的巧妙在于把满足条件的数
分成三个部分相加。
三、典型问题:输入b[i],r[i](分别为除数和余数)求a(被除数) , i∈[1,n]
四、形式化定义:
x≡r[1](mod b[1])
x≡r[2](mod b[2])
︰
x≡r[n](mod b[n])
设m[i]是除了b[i]的所有数的乘积m[i]=b[1]*b[2]*...*b[i-1]*b[i]...*b[n],
定义c[i]=m[i](m[i]¯1 mod b[i]) ; //第一、二步就是求c[i]
则有a≡∑a[i]*c[i] (mod M); M=∏b[i] //第三步
五、 典型题目:猪的安家
Andy和Mary养了很多猪。他们想要给猪安家。但是Andy没有足够的猪圈,很多猪只能够在一个猪圈安家。 举个例子,假如有16头猪,Andy建了3个猪圈,
为了保证公平,剩下1头猪就没有地方安家了。Mary生气了,骂Andy没有脑子,并让他重新建立猪 圈。这回Andy建造了5个猪圈,但是仍然有1头猪没有地方去,
然后Andy又建造了7个猪圈,但是还有2头没有地方去。Andy都快疯了。你对这个事情 感兴趣起来,你想通过Andy建造猪圈的过程,知道Andy家至少养了多少头
猪。
输入
输入包含多组测试数据。每组数据第 一行包含一个整数n (n <= 10) – Andy建立猪圈的次数,解下来n行,每行两个整数ai, bi( bi <= ai <= 1000), 表示Andy
建立了ai个猪圈,有bi头猪没有去处。你可以假定(ai, aj) = 1.
输出
输出包含一个正整数,即为Andy家至少养猪的数目。
样例输入
3
3 1
5 1
7 2
样例输出
16
代码如下:
1 #include<stdio.h>
2 int main()
3 {
4 int M,Mn,result;
5 int r[10],b[10],i,j,n;
6 while (~scanf("%d",&n))
7 {
8 result=0;
9 M=1;
10 for(i=0;i<n;i++)
11 {
12 scanf("%d %d",b+i,r+i);
13 M*=b[i];
14 }
15 for(i=0;i<n;i++)
16 {
17 Mn=M/b[i];
18 for(j=1;(Mn*j)%b[i]!=1;j++); //求乘法逆元,也可以用上面的扩展欧几里得算法
19 result+=Mn*r[i]*j;
20 }
21 printf("%d\n",result%M);
22 }
23 return 0;
24 }