数论相关内容总结——同余

同余相关内容总结与证明

- 同余的定义
若整数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} a1 (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} ap2 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
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} 123...(n1)n1
那么 将它乘 n n n 就得到 1 1 ∗ 2 ∗ 3 ∗ . . . ∗ ( n − 1 ) \frac{1}{1*2*3*...*(n-1)} 123...(n1)1
也就是 ( n − 1 ) ! (n-1)! n1 在模p下的逆元。

2. 扩展欧几里得算法
有时候 p p p 不是质数,但是我们仍然需要求逆元,这个时候要怎么办呢?
我们可以使用拓展欧几里得算法通过求解同余方程来得到逆元。
a ∗ x ≡ 1 ( m o d m ) a*x ≡ 1 (mod m) ax1(modm) 那么可以转换为 a ∗ x + k ∗ m = 1 a*x +k* m = 1 ax+km=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
using namespace std;
long long a,b,x,y,z,d;
long long exgcd(long long qwq,long long qaq){
     
	//if(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=xr1(modxr2(mod...xr1(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=1nmi 并且设 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 tiMi1(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=1nritiMi ( m o d (mod (mod M ) M) M)
x x x完整的解系为 x = x 0 + k ∗ M x=x_0+k*M x=x0+kM ( k ∈ Z ) (k\in Z) (kZ)
证明

为什么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
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=xr1(modxr2(mod...xr1(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={ xr1(modxr2(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=m1k1+r1x=m2k2+r2
k ∈ Z k∈ Z kZ
那么我们可以将 ① ① ② ② 联立
得到 m 1 ∗ k 1 + r 1 = m 2 ∗ k 2 + r 2 m_1*k_1+r_1=m_2*k_2+r_2 m1k1+r1=m2k2+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 m1k1m2k2=r2r1
哦! 似乎有一点眼熟
我们再来看看 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=r2r1即可
那么这个方程什么时候有解呢?
按照之前的推导 我们可以得出 只有在 g c d ( m 1 , m 2 ) ∣ ( r 2 − r 1 ) gcd(m_1,m_2)|(r_2-r_1) gcd(m1,m2)(r2r1) 的时候该方程才有解
用扩欧解出 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+klcm(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)m2k
又因为 x = m 1 ∗ k 1 + r 1 x=m_1*k_1+r_1 x=m1k1+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)m2m1=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+klcm(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
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

你可能感兴趣的:(数论,学习总结,算法)