贴一个dalao的证明:欧几里德算法与扩展欧几里德算法
1.欧几里得算法
O(logn)
int gcd(int a,int b)
{
if(!b)
{
return b;
}
return gcd(b,a%b);
}
2.贝祖定理
正整数a,b,那么一定存在非零整数x,y使得ax+by=gcd(a,b) 构造法证明——扩展欧几里得算法
贝祖定理扩展:
如果 ax+by=n有解,充要条件是 n 是gcd(a,b)的倍数
a是最大公约数的倍数,b是最大公约数的倍数,他们乘以一个系数相加,也应该是gcd(a,b)的倍数
3.扩展欧几里得算法
a x + b y = d ax+by=d ax+by=d, d = ( a , b ) = ( b , a % b ) = ( b , a − ⌊ a b ⌋ ∗ b ) = ( b , a − ⌊ a b ⌋ ∗ b + ⌊ a b ⌋ ∗ b ) = ( b , a ) d=(a,b)=(b,a\%b)=(b,a-⌊\frac{a}{b}⌋*b)=(b,a-⌊\frac{a}{b}⌋*b+⌊\frac{a}{b}⌋*b)=(b,a) d=(a,b)=(b,a%b)=(b,a−⌊ba⌋∗b)=(b,a−⌊ba⌋∗b+⌊ba⌋∗b)=(b,a)
b
y
+
(
a
%
b
)
x
=
d
by+(a\%b)x=d
by+(a%b)x=d
b
y
+
(
a
−
⌊
a
b
⌋
∗
b
)
x
=
d
by+(a-⌊\frac{a}{b}⌋*b)x=d
by+(a−⌊ba⌋∗b)x=d
a
x
+
b
(
y
−
⌊
a
b
⌋
∗
x
)
=
d
ax+b(y-⌊\frac{a}{b}⌋*x)=d
ax+b(y−⌊ba⌋∗x)=d,所以x是不需要变的,只需要y=y-a/b*x;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)//如果b为0
{
x=1,y=0;
return a;//(a,0)=a a*x+0*y=a x=1,y=0就是其中一组解
}
int d=exgcd(b,a%b,y,x);
y=y-a/b*x;
return d;
}
所求的x,y是不唯一的,只要求出了一个 x 0 , y 0 x_0,y_0 x0,y0 就可以求出所有的解
a ( x − b d ) + b ( y + a d ) = d a(x-\frac{b}{d})+b(y+\frac{a}{d})=d a(x−db)+b(y+da)=d
通解:
x
=
x
0
−
b
d
k
,
y
=
y
0
+
a
d
k
(
k
∈
Z
)
x=x_0-\frac{b}{d}k,y=y_0+\frac{a}{d}k\ (k∈Z)
x=x0−dbk,y=y0+dak (k∈Z)
求解线性同余方程
输入
2
2 3 6
4 3 5
输出
impossible
7
思路
eg. 2x≡3(mod 6)无解
4x≡3(mod 5) x=2;
a x ≡ b ( m o d b ) < = > 存 在 整 数 y , 使 得 a x = m y + b < = > a x − m y = b ax≡b\ (mod\ b) <=>存在整数y,使得ax=my+b<=>ax-my=b ax≡b (mod b)<=>存在整数y,使得ax=my+b<=>ax−my=b
令y’=y,则有 a x + m y ′ = b ax+my'=b ax+my′=b,该方程有解的等价条件是 (a,m)|b
a x + m y ′ = g c d ( a , m ) , b = k ∗ d , d = g c d ( a , m ) ax+my'=gcd(a,m),b=k*d,d=gcd(a,m) ax+my′=gcd(a,m),b=k∗d,d=gcd(a,m)
a x + m y ′ = d ax+my'=d ax+my′=d可用扩展欧几里得算法来求,再让等式两边同时扩大 b d \frac{b}{d} db倍
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=1e6+5;
const int mod=1e9+7;
typedef long long ll;
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)//如果b为0
{
x=1,y=0;
return a;//(a,0)=a a*x+0*y=a x=1,y=0就是其中一组解
}
int d=exgcd(b,a%b,y,x);
y=y-a/b*x;
return d;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)//数据量有点大,建议用scanf
{
int a,b,m;
scanf("%d%d%d",&a,&b,&m);
int x,y;
int d=exgcd(a,m,x,y);//注意这里应该对应a,m
ll ans=1ll*b/d*x%m;//注意不能写成x/d*b,因为d不一定能整除x
if(b%d)
printf("impossible\n");
else
printf("%lld\n",ans);
}
return 0;
}
注意ll ans=1ll*b/d*x%m;
要1ll*,不然会溢出。
4.中国剩余定理
所以
m
i
m_i
mi和
n
i
n_i
ni互质,
m
i
−
1
m_i^{-1}
mi−1表示
m
i
m_i
mi模
n
i
n_i
ni的逆
x = a 1 m 1 m 1 − 1 + a 2 m 2 m 2 − 1 + . . . + a k m k m k − 1 x=a_1m_1m_1^{-1}+a_2m_2m_2^{-1}+...+a_km_km_k^{-1} x=a1m1m1−1+a2m2m2−1+...+akmkmk−1,用扩展欧几里得来求逆( a x ≡ 1 m o d n ax≡1\ mod\ n ax≡1 mod n)
上式成立原因:
对于
n
1
n_1
n1来说,
m
1
和
n
1
m_1和n_1
m1和n1互质,
(
m
1
m
1
−
1
)
m
o
d
n
1
=
1
(m_1m_1^{-1})\ mod\ n_1=1
(m1m1−1) mod n1=1,后面其他项
(
m
i
m
i
−
1
)
m
o
d
n
1
(m_im_i^{-1})\ mod\ n_1
(mimi−1) mod n1均为0,因为
m
i
m_i
mi中包含
n
i
n_i
ni,所以就可以得到第一项
x
≡
a
1
m
o
d
n
1
x≡a_1\ mod\ n_1
x≡a1 mod n1
其他同理
例题
输入
2
8 7
11 9
输出
31
思路
x
m
o
d
a
1
=
m
1
x\ mod\ a_1=m_1
x mod a1=m1
x
=
k
1
a
1
+
m
1
x=k_1a_1+m_1
x=k1a1+m1
x
m
o
d
a
1
=
m
1
x\ mod\ a_1=m_1
x mod a1=m1
x
=
k
2
a
2
+
m
2
x=k_2a_2+m_2
x=k2a2+m2
要满足所有方程首先要满足前两个方程,然后我们可以两两合并地去求解
k
1
a
1
+
m
1
=
k
2
a
2
+
m
2
=
>
k
1
a
1
−
k
2
a
2
=
m
2
−
m
1
k_1a_1+m_1=k_2a_2+m_2 =>k_1a_1-k_2a_2=m_2-m_1
k1a1+m1=k2a2+m2=>k1a1−k2a2=m2−m1,已知
a
1
,
a
2
,
m
2
−
m
1
a_1,a_2,m_2-m_1
a1,a2,m2−m1,要求
k
1
,
k
2
k_1,k_2
k1,k2用扩展欧几里得
,上式有解等价于
(
a
1
,
a
2
)
∣
m
2
−
m
1
(a_1,a_2)|m_2-m_1
(a1,a2)∣m2−m1
一旦我们求出了一组解
k
1
,
k
2
k_1,k_2
k1,k2,通解就可表示为
k
1
′
=
k
1
+
k
a
2
d
,
k
2
′
=
k
2
+
k
a
1
d
k_1'=k_1+k\frac{a_2}{d},k_2'=k_2+k\frac{a_1}{d}
k1′=k1+kda2,k2′=k2+kda1 前面加一个后面加一个,一减就抵消了
x
=
k
1
a
1
+
m
1
=
(
k
1
+
k
a
2
d
)
a
1
+
m
1
=
a
1
k
1
+
m
1
+
k
a
1
a
2
d
x=k_1a_1+m_1=(k_1+k\frac{a_2}{d})a_1+m_1=a_1k_1+m_1+k\frac{a_1a_2}{d}
x=k1a1+m1=(k1+kda2)a1+m1=a1k1+m1+kda1a2 x的所有解
x
0
=
a
1
k
1
+
m
1
x_0=a_1k_1+m_1
x0=a1k1+m1,
a
1
,
a
2
a_1,a_2
a1,a2的最小公倍数
a
=
a
1
a
2
d
a=\frac{a_1a_2}{d}
a=da1a2 =>
x
=
x
0
+
k
a
x=x_0+ka
x=x0+ka(这就是前两个式子合并后的结果)合并n-1次,就可以把n个方程组合成一个了
当只有一个方程时,要求最小非负整数x:
最后要求的就是
x
m
o
d
a
≡
x
0
x\ mod\ a≡x_0
x mod a≡x0,
x
和
x
0
x和x_0
x和x0模a同余,就是求
x
0
m
o
d
a
x_0\ mod\ a
x0 mod a的正余数
最后的a会是a1...an的最小公倍数,很有可能会溢出整数范围,所以一般方程式的个数不会太多
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=1e6+5;
const int mod=1e9+7;
typedef long long ll;
using namespace std;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)//如果b为0
{
x=1,y=0;
return a;//(a,0)=a a*x+0*y=a x=1,y=0就是其中一组解
}
ll d=exgcd(b,a%b,y,x);
y=y-a/b*x;
return d;
}
int main()
{
int n;
scanf("%d",&n);
ll x=0,m1,a1;
cin>>a1>>m1;
for(int i=0;i<n-1;i++)
{
ll a2,m2;
cin>>a2>>m2;
ll k1,k2;
ll d=exgcd(a1,a2,k1,k2);
if((m2-m1)%d)
{
x=-1;
break;
}
//exgcd求的是k1a1-k2a2=d的解,要把右部变成(m2-m1)的解,等式两边同乘(m2-m1)/d
k1*=(m2-m1)/d;
ll t=a2/d;//k1'=k1+a2/d*k
k1=(k1%t+t)%t;//把k1变成这个方程的最小正整数解
x=k1*a1+m1;//x0=k1a1+m1
ll a=abs(a1/d*a2);//最小公倍数 a=a1a1/d
//x=x0+ka x≡m(%a) 构建新的迭代
m1=k1*a1+m1;//m1=x0;
a1=a;
}
if(x!=-1)
x=(x%a1+a1)%a1;//取x%a
cout<<x<<endl;
return 0;
}
这道题数据比较极限,要让每个k都变得尽可能地小,所以要k1=(k1%t+t)%t;