同余相关内容总结与证明
- 同余的定义
若整数a和整数b除以正整数m的余数相等,则称a,b模m同余,记作a≡b(mod m)
- 欧几里得算法
gcd(a,b)=gcd(b,a%b);
证明
a=kb+r(k是整数且r<b) r=a%b;
假设d为a,b的一个公因数;
r=a-kb; r/d=(a/d)+k*(b/d);
等号右侧是一个整数 所以等号左侧也是一个整数
即 r可以被d整除,所以d是r的一个因数
而r=a%b; d同时是a,b的一个因数;
所以d也同时是a%d的一个因数
最大公因数属于因数
则gcd(a,b)=gcd(b,a%b);
证毕
- 裴蜀定理 Bézout’s 定理
对于任意整数a,b,存在一对整数x,y,满足ax+by=gcd(a,b)
证明(以及求解思路):
求gcd时(欧几里得算法) 最后一步时b=0,则显然有x=1 y=0满足a*1+0*0=gcd(a,0) gcd(a,0)=a; (推论一)
而当b>0的时候 设x1*a+y1*b=gcd(a,b) ① 则也有 x2*b+y2*(a%b)=gcd(b,a%b); ②
因为gcd(a,b)=gcd(b,a%b) 所以有①式左=②式右
②式右=x2*b+y2*(a-[a/b]*b)
=x2*b+y2*a-[a/b]*b
=y2*a+(x2-[a/b])*b;
我们再来和①式对比一下
x1*a+y1*b
由对应系数相等得
x1=y2; y1=x2-[a/b];
而gcd求解的过程是从b=0后往上返的 即x2,y2等会先于x1,y1求出
由推论一往上返 必能求出对应的每一个x,y的值
证毕
必须要强调的是,我们所解出的 x0,y0只是当前方程的一组特解
设d=gcd(a,b)
对于ax+by=gcd(a,b) 它的通解应该是x=x0+(b/d)
∗
*
∗k; y=y0-(a/d)
∗
*
∗k
这是由于 我们要得到每一个满足条件(ax+by=gcd(a,b))的x与y
但我们起码得保证b/d 与a/d一定是整数 那么 a
∗
*
∗(b/d)
∗
*
∗k - b
∗
*
∗(a/d)
∗
*
∗k=0。
那么 更一般的 对于一个方程 ax+by=c。
它有解 当且仅当 d|c。
为什么呢? 我们可以反证一下
因为d是a,b的gcd 则我们可将 ax+by=c转换为
d*(ax+by)=c
若d不是c的因子 等式两边不可能成立 (或:由因数的意义可知,d必为c的一个因子)
故d一定是c的因子 证毕
那么对于该方程 通解应该为x=x0
∗
*
∗(c/d)+(b/d)
∗
*
∗k; y=y0
∗
*
∗(c/d)-(a/d)
∗
*
∗k
为什么含k项不用乘c呢?
这是由于 含k的一项是我们为了得到通解 而同时加上(减去)的一个数 我们一定希望他在满足题意的情况下尽可能的小 我们发现这个数是不受到
c
d
\frac {c}{d}
dc 的倍数关系影响的 不用乘c的情况下仍然可以使等式成立。
所以k项不用乘c
此外 在求解时 如果有c<0的情况 因为有 ax+by=c 即ax≡ c(mod b) 所以可以将c处理为模b意义下的最小非负整数然后再求解
- 逆元
定义: 对于互质的整数a,m,一定存在一个整数x,使得a*x≡1(mod m)即x=
a
−
1
a^{-1}
a−1 (mod m)
那么我们应有一个疑问 为什么要保证m,a互质呢?
必须要保证m,a互质的原因如下:
因为a*x≡1(mod m) 设有一整数y,我们可以把它转化为:
a*x+m*y=1;
我们由Bézout’s 定理可知 这个式子有解当且仅当gcd(a,b)|1
则a,b必须互质
那么我们求逆元的意义何在呢?
在于在模p的情况下可以避免做除法而将他转换为乘法
例如 我们想要在模p的情况下除以一个数a 那么我们就可以乘以它的逆元然后对p取模,就避免了我们不想看见的精度丢失的情况。
在这里我介绍两种求逆元的方法
1.费马小定理
在模p的情况下 p是质数时成立 (注:p不是质数时不一定成立 详见某度“伪素数”概念)
a
ϕ
(
p
)
−
1
a^{\phi(p)-1}
aϕ(p)−1≡
a
p
−
2
a^{p-2}
ap−2≡
1
a
\frac {1}{a}
a1
当p是质数时 那么在
[
1
,
p
]
[1,p]
[1,p] 的范围内 除了以外所有的数都与p互质 所以
ϕ
(
p
)
−
1
\phi(p)-1
ϕ(p)−1=p-2
证明
若a,b,c为任意三个整数 m为正整数且gcd(m,c)=1,则当a*c≡b*c(mod m)时 有a≡b (推论1)
因为a*c≡b*c(mod m) 则可设a*c=k1*m+r ① b*c=k2*c+r ②
①-②得 a*c-b*c=(k1-k2)*m 所以a*c-b*c≡0(mod m)
又因为gcd(m,c)=1 所以m必为(a-b)的一个因数
则得到a-b≡0(mod m) 所以a≡b(mod m)
推论1 证毕
若m是一个整数并且m>1,b是一个整数并且gcd(m,b)=1,那么 如果a[1],a[2]...a[n]可构成模m意义下的完全剩余系,那么a[1]*b,a[2]*b...a[n]*b也可以构成模m意义下的完全剩余系 (推论二)
反证法:
若a[1]*b≡a[2]*b (mod m) 则此时无法构成模m意义下的完全剩余系
因为m,b互质 由推论一可得 a[1]≡a[2] (mod m)
这与条件相违背 所以假设不成立
推论2 证毕
由推论1,2证费马小定理
因为p是质数 所以a[1],a[2]...a[n]分别为1,2...p-1.
a[1]*a[2]*...*a[n]≡a[1]*b,a[2]*b...a[n]*b(mod m)
(注:这里可能不是一一对应关系 比如 a[1]*b%m不一定等于a[1],而是可能等于其他的a[i])
那么我们可以得到
(p-1)!=(p-1)!*a^(p-1)
两边同时约去(p-1)! 得到 1=a^(p-1);
所以 1/a= a^(p-2)
证毕
那么具体如何实现呢
当然是直接用快速幂啦!
当然 有时候我们会求从
[
1
,
n
]
[1,n]
[1,n]所有逆元 那么我们这时通常用以下代码实现
#include<bits/stdc++.h>
using namespace std;
long long ny[3000100],jc[3000100],n,p;
long long qui(long long a,long long b){
long long ans=1;
while(b){
if(b&1) ans=(ans*a)%p;
a=(a*a)%p;
b=b>>1;
}
return ans;
}
void work(){
jc[0]=1;
for(int i=1;i<=n;i++) jc[i]=(jc[i-1]*i)%p; //求出从1到n的阶乘
ny[n]=qui(jc[n],p-2); //算出阶乘n的逆元
for(int i=n-1;i>=1;i--) ny[i]=(ny[i+1]*(i+1))%p;//求出[1,n-1]的阶乘的逆元 证明见下
}
int main(){
freopen("invele.in","r",stdin);
freopen("invele.out","w",stdout);
scanf("%lld%lld",&n,&p);
work();
for(int i=1;i<=n;i++){
printf("%lld\n",(ny[i]*jc[i-1])%p); //i!的逆元乘上(i-1)! 剩下的即为i的逆元
}
return 0;
}
证明: 对于某个阶乘
n
!
n!
n! 来说 它的逆元可看成在模
p
p
p 条件下的
1
1
∗
2
∗
3
∗
.
.
.
∗
(
n
−
1
)
∗
n
\frac{1}{1*2*3*...*(n-1)*n}
1∗2∗3∗...∗(n−1)∗n1
那么 将它乘
n
n
n 就得到
1
1
∗
2
∗
3
∗
.
.
.
∗
(
n
−
1
)
\frac{1}{1*2*3*...*(n-1)}
1∗2∗3∗...∗(n−1)1
也就是
(
n
−
1
)
!
(n-1)!
(n−1)! 在模p下的逆元。
2. 扩展欧几里得算法
有时候
p
p
p 不是质数,但是我们仍然需要求逆元,这个时候要怎么办呢?
我们可以使用拓展欧几里得算法通过求解同余方程来得到逆元。
a
∗
x
≡
1
(
m
o
d
m
)
a*x ≡ 1 (mod m)
a∗x≡1(modm) 那么可以转换为
a
∗
x
+
k
∗
m
=
1
a*x +k* m = 1
a∗x+k∗m=1
是不是有一丝眼熟?
我们和Bézout’s 定理中的
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd (a,b)
ax+by=gcd(a,b)对照一下
所以我们也可以用同样的方法求解
x
x
x
我们上文已经知道 这个方程有解当且仅当
g
c
d
(
a
,
m
)
∣
1
gcd(a,m)|1
gcd(a,m)∣1
所以 只要
a
,
m
a , m
a,m 互质,我们就可以使用这个方法求逆元啦!
方法就是上文提到的 Bézout’s 定理,解出来的逆元就是
x
x
x
附代码
#include<bits/stdc++.h>
using namespace std;
long long a,b,x,y,z,d;
long long exgcd(long long qwq,long long qaq){
//if(qwq<qaq) swap(qaq,qwq);
if(qaq==0){
x=1; y=0; return qwq;
}
d=exgcd(qaq,qwq%qaq);
z=x; x=y; y=z-(qwq/qaq)*y;
return d;
}
int main(){
freopen("mod.in","r",stdin);
freopen("mod.out","w",stdout);
cin>>a>>b;
exgcd(a,b);
printf("%lld",(x%b+b)%b);
return 0;
}
中国剩余定理
这个定理解决的是:如何求出一个
x
x
x 使
x
=
{
x
≡
r
1
(
m
o
d
m
1
)
x
≡
r
2
(
m
o
d
m
2
)
.
.
.
x
≡
r
1
(
m
o
d
m
n
)
x= \left \{ \begin{aligned} x ≡ r_1 (mod&& m_1)\\ x ≡ r_2 (mod &&m_2)\\ ...\\ x ≡ r_1 (mod &&m_n) \end{aligned} \right.
x=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧x≡r1(modx≡r2(mod...x≡r1(modm1)m2)mn)
这些条件同时成立
中国剩余定理是在
m
1
,
m
2
.
.
.
m
n
m_1,m_2...m_n
m1,m2...mn 任意两两之间互质的情况下去解决这一问题的。
采用的方法是构造
首先我们构造出
M
=
∏
i
=
1
n
m
i
M= \prod\limits_{i=1}^nm_i
M=i=1∏nmi 并且设
M
i
=
M
m
i
M_i= \frac{M}{m_i}
Mi=miM,以及求出
t
i
∗
M
i
≡
1
(
m
o
d
t_i*M_i≡ 1 (mod
ti∗Mi≡1(mod
m
i
)
m_i)
mi)
因为
M
i
M_i
Mi与
m
i
m_i
mi互质,所以这个
t
i
t_i
ti一定是存在的
那么在模
M
M
M的意义下,
x
0
=
∑
i
=
1
n
r
i
t
i
M
i
x_0=\sum\limits_{i=1}^{n}r_it_iM_i
x0=i=1∑nritiMi
(
m
o
d
(mod
(mod
M
)
M)
M)
x
x
x完整的解系为
x
=
x
0
+
k
∗
M
x=x_0+k*M
x=x0+k∗M
(
k
∈
Z
)
(k\in Z)
(k∈Z)
证明
为什么x0是一个特解呢?
因为 对于任意两项 r1t1M1 ① 与r2t2M2 ②,以及对应的m1,m2来说
①+② mod m1时 因为m1∈M2 那么②会被模去
而t1M1在mod m1的情况下为1
最后得到的结果就是r1
对于 mod m2的时候同理
用数学归纳法可证得 该方法推广到[1,n]都成立 所以 x0是一个符合答案的特解
为什么完整的解系是x=x0+k*M呢?
因为m1,m2,m3...,mn两两互质
而在模任意一个mi的情况下 都要保证最后留下来的数是ri
因此只有在加的数是M的倍数的时候符合题意
证毕
代码
#include<bits/stdc++.h>
using namespace std;
long long n,mo[15],yu[15],M=1,ny,ans,x,y,z;
void exgcd(long long a,long long b){
if(b==0) {
x=1; y=0; return;
}
exgcd(b,a%b);
z=x; x=y; y=z-(a/b)*y;
}
int main(){
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&mo[i],&yu[i]);
M*=mo[i];//构造M
}
long long Mi;
for(int i=1;i<=n;i++){
Mi=M/mo[i];//构造Mi
exgcd(Mi,mo[i]);//求逆元
ans=(ans+((yu[i]*Mi%M)*x))%M;
}
printf("%lld",(ans%M+M)%M);
return 0;
}
因为这里并不保证 m i m_i mi是质数 所以选择扩展欧几里得算法求逆元
扩展中国剩余定理
我们思考一下 如果
m
i
m_i
mi之间彼此不互质 那么上面的中国剩余定理求法还可以使用吗?
答案是否定的
上面的求法的精髓在于我们可以求出每个
M
i
M_i
Mi的在模
m
i
m_i
mi的情况下的逆元
t
i
t_i
ti使得两项乘积在模
m
i
m_i
mi的情况下为1
但是在
m
i
m_i
mi不一定互质的情况下
M
i
M_i
Mi与
m
i
m_i
mi不一定互质 因此不一定能求出对应的逆元
t
i
t_i
ti
因此我们需要 扩展中国剩余定理
那么我们如何做呢?
首先对于
x
=
{
x
≡
r
1
(
m
o
d
m
1
)
x
≡
r
2
(
m
o
d
m
2
)
.
.
.
x
≡
r
1
(
m
o
d
m
n
)
x= \left \{ \begin{aligned} x ≡ r_1 (mod&& m_1)\\ x ≡ r_2 (mod &&m_2)\\ ...\\ x ≡ r_1 (mod &&m_n) \end{aligned} \right.
x=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧x≡r1(modx≡r2(mod...x≡r1(modm1)m2)mn)
我们可以考虑先提出其中的两项
x
=
{
x
≡
r
1
(
m
o
d
m
1
)
x
≡
r
2
(
m
o
d
m
2
)
x= \left \{ \begin{aligned} x ≡ r_1 (mod&& m_1)\\ x ≡ r_2 (mod &&m_2)\\ \end{aligned} \right.
x={x≡r1(modx≡r2(modm1)m2)
也就是等价于
{
x
=
m
1
∗
k
1
+
r
1
①
x
=
m
2
∗
k
2
+
r
2
②
\left\{ \begin{aligned} x=m_1*k_1+r_1 ①\\ x=m_2*k_2+r_2 ② \end{aligned} \right.
{x=m1∗k1+r1①x=m2∗k2+r2②
k
∈
Z
k∈ Z
k∈Z
那么我们可以将
①
①
①
②
②
②联立
得到
m
1
∗
k
1
+
r
1
=
m
2
∗
k
2
+
r
2
m_1*k_1+r_1=m_2*k_2+r_2
m1∗k1+r1=m2∗k2+r2
移项得到
m
1
∗
k
1
−
m
2
∗
k
2
=
r
2
−
r
1
m_1*k_1-m_2*k_2=r_2-r_1
m1∗k1−m2∗k2=r2−r1
哦! 似乎有一点眼熟
我们再来看看
a
x
+
b
y
=
c
ax+by=c
ax+by=c
所以我们只要让
x
=
m
1
x=m_1
x=m1
y
=
m
2
y=m_2
y=m2
c
=
r
2
−
r
1
c=r_2-r_1
c=r2−r1即可
那么这个方程什么时候有解呢?
按照之前的推导 我们可以得出 只有在
g
c
d
(
m
1
,
m
2
)
∣
(
r
2
−
r
1
)
gcd(m_1,m_2)|(r_2-r_1)
gcd(m1,m2)∣(r2−r1) 的时候该方程才有解
用扩欧解出
k
1
,
k
2
k_1,k_2
k1,k2之后 我们可以回代解出一个
x
0
x_0
x0
依据之前中国剩余定理的推导 那么可以证明出
x
x
x 的解系为
x
=
x
0
+
k
∗
l
c
m
(
m
1
,
m
2
)
x=x_0+k*lcm(m_1,m_2)
x=x0+k∗lcm(m1,m2)
除此,同时我们可以用上面Bézout’s 定理推出的性质证明
因为
k
1
k_1
k1 是我们通过解类似于
a
x
+
b
y
=
c
ax+by=c
ax+by=c的方程得到的
所以它的解系满足
k
1
=
k
1
(
特
解
)
+
m
2
g
c
d
(
m
1
,
m
2
)
∗
k
k_1=k_ 1 (特解)+\frac{m_2}{gcd(m_1,m_2)}*k
k1=k1(特解)+gcd(m1,m2)m2∗k
又因为
x
=
m
1
∗
k
1
+
r
1
x=m_1*k_1+r_1
x=m1∗k1+r1
所以
x
x
x和其解的距离为
m
2
∗
m
1
g
c
d
(
m
1
,
m
2
)
=
l
c
m
(
m
1
,
m
2
)
\frac{m_2*m_1}{gcd(m_1,m_2)}=lcm(m_1,m_2)
gcd(m1,m2)m2∗m1=lcm(m1,m2)
证毕。
因为
x
=
x
0
+
k
∗
l
c
m
(
m
1
,
m
2
)
x=x_0+k*lcm(m_1,m_2)
x=x0+k∗lcm(m1,m2)
那么我们令
M
=
l
c
m
(
m
1
,
m
2
)
M=lcm(m_1,m_2)
M=lcm(m1,m2)
R
=
x
0
R=x_0
R=x0
我们就完成了将两个方程合并成一个的过程
接下来只要不断地合并就可以了!
代码
#include<bits/stdc++.h>
using namespace std;
long long read()
{
long long num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
} //快读
long long t,M,R,m[100100],r[100100],x,y,gcd,k;
long long exgcd(long long a,long long b){
if(b==0){
x=1; y=0; return a;
}
long long qwq=exgcd(b,a%b);
long long qaq=x; x=y; y=qaq-(a/b)*y;
return qwq;
} //扩欧
long long mul(long long a,long long b,long long mo){
long long ans=0;
if(a>b) swap(a,b);
a%=mo; b%=mo;
while(b>0){
if(b&1) ans=(ans+a)%mo;
a=(a+a)%mo;
b=b>>1;
}
ans=(ans%mo+mo)%mo; //注意这里 若是不这么处理的话可能会有返回值为负数的情况
return ans;
}//龟速乘
int main(){
freopen("strange.in","r",stdin);
freopen("strange.out","w",stdout);
t=read();
for(int i=1;i<=t;i++){
m[i]=read();
r[i]=read();
}
M=m[1]; R=r[1];
for(int i=2;i<=t;i++){
long long qwq=(((r[i]-R)%m[i])+m[i])%m[i]; //为什么可以这么处理呢?
//这是由于我们相当与在解同余式k1*M ≡ r[i]-R(mod mi) 也就是k1*M+k2*mi=r[i]-R
//也就是先求出k11*M≡ gcd(r[i]-R,M) (mod mi)之后再把k11乘上对应的倍数得到k1 因此我们可以直接先对r[i]-R取模
//取模的意义在于 r[i]-R可能是一个负数 而这可能会给运算带来不必要的麻烦
gcd=exgcd(M,m[i]);
if((qwq%gcd)!=0) {
printf("-1");
return 0;
}
k=mul(((qwq)/gcd),x,(m[i]/gcd)); //龟速乘得到最小非负整数k
R=k*M+R;//求出特解x0
M=(M/gcd)*m[i];//求出对应的模数
R=((R%M)+M)%M;//求出通解中的最小正整数x
}
printf("%lld",R);
return 0;
}
后记
同余的内容总结到这里也算是暂时告一段落了,在总结这些学过的东西的过程中,我花了大量的时间再去复习(重学),以及再去研究一些以前曾经做过的推导和证明。希望这个总结能有所意义,也希望它在日后还有所补充。
如果有任何问题(这个东西真的会有别人看吗啊喂) 可以通过评论等方式咨询 ,如果在我能力范围内的话会认真答疑,不在的话也会再去考虑。
2021.5.5