各种友(e)善(xin)数论总集,从入门到绝望6---同余系列!(ex)GCD,(ex)CRT

参考文献

SCY资料、手推

博客:https://www.cnblogs.com/MashiroSky/p/5918158.html很好的阐述了中国剩余定理的公式等等等等,反正我看了这个博客很快就懂了。

GCD的家族

GCD

如果我们要求两个数字的最大公约数怎么求?

如果两个数字 a , b a,b a,b存在最大公约数 k k k,那么把 a a a写成 x k xk xk,把 b b b写成 y k yk yk

那么我们知道 b % a = ( y % x ) k b\%a=(y\%x)k b%a=(y%x)k,但是 y y y又与 x x x互质,所以仅当 x = 1 x=1 x=1时, y y y为最大公约数,此时 a = k a=k a=k也就是最大公约数,而且我们也知道 y % x y\%x y%x也是与x互质的。

所以我们可以设新的 a ′ a' a与新的 b ′ b' b a ′ = b % a a'=b\%a a=b%a, b ′ = a b'=a b=a,当 a ′ = 0 a'=0 a=0时, b b b就是最大公约数。

而且一开始 a > b a>b a>b也不怕,做了一次交换后就换过来了( b % a = b b\%a=b b%a=b)

int  gcd(int  x,int  y)
{
    
    
	if(x==0)return  y;
	return  gcd(y%x,x);
}

扩展:更相减损法

另外补充一种GCD的形式。

二进制的GCD用的是更相减损法。

首先,我们有两个数字 x , y x,y x,y

  1. x % 2 = = 0 , y % 2 = = 0 , g c d ( x , y ) = g c d ( x > > 1 , y > > 1 ) ∗ 2 x\%2==0,y\%2==0,gcd(x,y)=gcd(x>>1,y>>1)*2 x%2==0,y%2==0,gcd(x,y)=gcd(x>>1,y>>1)2
  2. x % 2 = = 0 , y % 2 = = 1 , g c d ( x , y ) = g c d ( x > > 1 , y ) x\%2==0,y\%2==1,gcd(x,y)=gcd(x>>1,y) x%2==0,y%2==1,gcd(x,y)=gcd(x>>1,y)
  3. x % 2 = = 1 , y % 2 = = 0 , g c d ( x , y ) = g c d ( x , y > > 1 ) x\%2==1,y\%2==0,gcd(x,y)=gcd(x,y>>1) x%2==1,y%2==0,gcd(x,y)=gcd(x,y>>1)
  4. x % 2 = = 1 , y % 2 = = 1 , x > = y , g c d ( x , y ) = g c d ( ( x − y ) > > 1 , y ) x\%2==1,y\%2==1,x>=y,gcd(x,y)=gcd((x-y)>>1,y) x%2==1,y%2==1,x>=y,gcd(x,y)=gcd((xy)>>1,y),其实这里的 x − y x-y xy不用除 2 2 2也可以,只是这样写更快,反正都是偶数。
inline  LL  gcd(LL  x,LL  y)
{
    
    
	int  ans=0;
	while(x  &&  y)
	{
    
    
		if(x&1  &&  y&1)
		{
    
    
			y>x?x^=y^=x^=y:0;
			x=(x-y)>>1;
		}
		else  if(x&1)y>>=1;
		else  if(y&1)x>>=1;
		else  x>>=1,y>>=1,ans++;
	}
	return  (x+y)<<ans;
}

常数好像比GCD更小,复杂度都是 l o g log log,但是貌似跑起来差不多,写写吧,反正都差不多了,还更稳一点,你说是吧。

扩展欧几里得(EXGCD)

如何求 a x + b y = c ax+by=c ax+by=c的一个整数解?

尽看扩展欧几里得。

首先,我们设 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)

那我们就想了,如果有个方程为 0 x + g c d ( a , b ) y = g c d ( a , b ) 0x+gcd(a,b)y=gcd(a,b) 0x+gcd(a,b)y=gcd(a,b),不就很妙了吗?
y = 1 , x y=1,x y=1,x随便等于什么都不影响最终结果,只不过一般取0,怕爆long long。

然后我们再证一个:

当我们解出了 ( b % a ) x + a y = g c d ( a , b ) (b\%a)x+ay=gcd(a,b) (b%a)x+ay=gcd(a,b),怎么推到 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)

我们可以推导一下( ⌊ x ⌋ ⌊x⌋ x为向下取整):
a x + b y = ( b − ⌊ b / a ⌋ ∗ a ) x ′ + a y ′ ax+by=(b-⌊b/a⌋*a)x'+ay' ax+by=(bb/aa)x+ay
a x + b y = − a ∗ ⌊ b / a ⌋ x ′ + a y ′ + b x ′ ax+by=-a*⌊b/a⌋x'+ay'+bx' ax+by=ab/ax+ay+bx
a x + b y = a ( y ′ − ⌊ b / a ⌋ x ′ ) + b x ′ ax+by=a(y'-⌊b/a⌋x')+bx' ax+by=a(yb/ax)+bx

⌊ b / a ⌋ ⌊b/a⌋ b/a就是C++的 b / a b/a b/a

所以,我们发现 b % a b\%a b%a a a a就是 g c d gcd gcd过程,所以最后也会得到 0 0 0 g c d ( a , b ) gcd(a,b) gcd(a,b)

然后再看:如果 g c d ( a , b ) ∣ c gcd(a,b)|c gcd(a,b)c的话,设 d = c / g c d ( a , b ) d=c/gcd(a,b) d=c/gcd(a,b),然后把 x , y x,y x,y直接乘以 d d d就没什么毛病了。

但是如果不整除就无解,我们又可以证一证:


首先 g c d ( a , b ) ∣ c gcd(a,b)|c gcd(a,b)c是肯定有解的,我们可以知道,而且 a x + b y = c ax+by=c ax+by=c可以类似于 a x ≡ c m o d    b ax≡c\mod b axcmodb,我们可以根据同余的性质得出我们可以把 a , b , c a,b,c a,b,c的同一个因数 g c d ( a , b ) gcd(a,b) gcd(a,b)提出,得到 a ′ , b ′ , c ′ a',b',c' a,b,c,化成 a ′ x ≡ c ′ m o d    b ′ a'x≡c'\mod b' axcmodb,而且a’与b’不互质,那么我们可以得出(欧拉定理证明过程可以说明),同余方程 a x ≡ c m o d    b ax≡c\mod b axcmodb a , b a,b a,b互质时有解,或者说当 c c c整除 g c d ( a , b ) gcd(a,b) gcd(a,b)时有解。(好像说偏了

然后,如果 g c d ( a , b ) gcd(a,b) gcd(a,b)与c不整除,我们再设 z = g c d ( a , b ) , c = k z + d z=gcd(a,b),c=kz+d z=gcd(a,b),c=kz+d

那么我们把原式化为: a ′ z x + b ′ z y = k z + d a'zx+b'zy=kz+d azx+bzy=kz+d同除 z z z可得

a ′ x + b ′ y = k + d z a'x+b'y=k+ \frac{d}{z} ax+by=k+zd

但是 d < z d<z d<z所以 d z \frac{d}{z} zd是个分数,但是 a ′ x + b ′ y a'x+b'y ax+by都是整数,所以无解

当然这个同时也有个推论。------https://blog.csdn.net/bcr_233/article/details/87897021
a x + b y + c z + ⋯ + n m = f ax+by+cz+⋯+nm=f ax+by+cz++nm=f(其中 a , b , c , … , n , f a , b , c , a,b,c,…,n,fa,b,c, a,b,c,,n,fa,b,c,为整数),
它有整数解的充要条件是 f f f g c d ( a , b , c , … , n ) gcd(a,b,c,…,n) gcd(a,b,c,,n)的整数倍。

必要性就不证明了,充分性说一下,我们采用数学归纳法,对于有 n n n个未知数: a 1 x 1 + a 2 x 2 + . . . + a n x n a_1x_1+a_2x_2+...+a_nx_n a1x1+a2x2+...+anxn而言,我们已经证明了 a 1 x 1 + a 2 x 2 + . . . + a n − 1 x n − 1 = g c d ( a 1 , a 2 , . . . ) a_1x_1+a_2x_2+...+a_{n-1}x_{n-1}=gcd(a_1,a_2,...) a1x1+a2x2+...+an1xn1=gcd(a1,a2,...)一定有解,那么我们就可以把前面的统一换成: g c d ( a 1 , a 2 , a 3 . . . ) x n + 1 + a n x n gcd(a_1,a_2,a_3...)x_{n+1}+a_nx_n gcd(a1,a2,a3...)xn+1+anxn,然后就变成了二个元,所以 g c d ( a 1 , a 2 , a 3 . . . , a n − 1 ) x n + 1 + a n x n = g c d ( g c d ( a 1 , a 2 , a 3 . . . , a n − 1 ) , a n ) = g c d ( a 1 , a 2 , a 3 . . . , a n ) gcd(a_1,a_2,a_3...,a_{n-1})x_{n+1}+a_nx_n=gcd(gcd(a_1,a_2,a_3...,a_{n-1}),a_n)=gcd(a_1,a_2,a_3...,a_n) gcd(a1,a2,a3...,an1)xn+1+anxn=gcd(gcd(a1,a2,a3...,an1),an)=gcd(a1,a2,a3...,an)必定有解,得证。


至此,结束了(感觉又过了一个世纪)!

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
LL  exgcd(LL  a,LL  b,LL  &x,LL  &y/*引用*/)
{
    
    
	if(a==0)
	{
    
    
		x=0;y=1;//0x+gcd(a,b)y=gcd(a,b) 
		return  b;
	}
	else
	{
    
    
		LL  tx,ty;
		LL  d=exgcd(b%a,a,tx,ty);
		x=ty-(b/a)*tx;//公式 
		y=tx;//公式 
		return  d;
	}
}
int  main()
{
    
    
	LL  a,b,c,x,y;scanf("%lld%lld%lld",&a,&b,&c);
	LL  d=exgcd(a,b,x,y);
	if(c%d!=0)printf("no solution!\n");
	else
	{
    
    
		c/=d;
		printf("%lld %lld\n",x*c,y*c);
	}
	return  0;
}

其实也没什么难度。

中国剩余定理CRT及其扩展EXCRT

同余方程(基本的EXGCD应用)

题目描述

【题意】
已知a,b,m,求x的最小正整数解,使得ax≡b(mod m)
【输入格式】
一行三个整数 a,b,m。 1 ≤ a,b,m ≤ 10^9 
【输出格式】
一行一个整数x,无解输出"no solution!"
【样例输入】
2 5 7
【样例输出】
6

我们会发现 a x ≡ b ax≡b axb( m o d mod mod m m m)其实就是 a x − m y = b ax-my=b axmy=b也就是 a x + m y = b ax+my=b ax+my=b m m m的正负无多大必要,反正只是求 x x x,而且 m > 0 m>0 m>0方便的地方在于 g c d gcd gcd为正数)

然后我们运用扩展欧几里德求出 x x x,但是如何求出最小的 x x x

学过不定式的人都知道,当 a x ax ax [ a , b ] [a,b] [a,b]时, m y my my也可以相应的加 [ a , b ] [a,b] [a,b]时( [ a , b ] [a,b] [a,b]就是 a , b a,b a,b的最小公倍数), x , y x,y x,y能得出另外一组整数解,也就是 x − [ a , b ] / a , y + [ a , b ] / b x-[a,b]/a,y+[a,b]/b x[a,b]/a,y+[a,b]/b,又因为 [ a , b ] = a ∗ b / ( a , b ) [a,b]=a*b/(a,b) [a,b]=ab/(a,b),所以就是 x − b / ( a , b ) , y − a / ( a , b ) x-b/(a,b),y-a/(a,b) xb/(a,b),ya/(a,b) ( a , b ) (a,b) (a,b) g c d ( a , b ) gcd(a,b) gcd(a,b))。

而且 [ a , b ] [a,b] [a,b]是能同时被 a , b a,b a,b整除的最小的数字。

所以,我们可以用 d = g c d ( a , b ) , x = ( x % ( b / d ) + b / d ) % ( b / d ) d=gcd(a,b),x=(x\%(b/d)+b/d)\%(b/d) d=gcd(a,b),x=(x%(b/d)+b/d)%(b/d)得出 x x x最小的正整数解

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
LL  exgcd(LL  a,LL  b,LL  &x,LL  &y)
{
    
    
	if(a==0)
	{
    
    
		x=0;y=1;
		return  b;
	}
	else
	{
    
    
		LL  tx,ty;
		LL  d=exgcd(b%a,a,tx,ty);
		x=ty-(b/a)*tx;
		y=tx;
		return  d;
	}
}
LL  zbs(LL  x){
    
    return  x<0?-x:x;}
int  main()
{
    
    
	LL  a,b,c,x,y;scanf("%lld%lld%lld",&a,&c,&b);
	LL  d=exgcd(a,b,x,y);//求扩展欧几里得 
	if(c%d!=0)printf("no solution!\n");
	else
	{
    
    
		c/=d;
		x*=c;
		LL  k=b/d;
		x=((x%k)+k)%k;//最小的正整数解 
		printf("%lld\n",x);
	}
	return  0;
}

中国剩余定理CRT

题目描述

【题意】 
同余方程是这样的:已知a,b,n,求x的最小正整数解,使得ax=b(mod m)
同余方程组是这样:也是求x的最小正整数解,但已知b数组和m数组的情况下,
x=b[1](mod m[1]),
x=b[2](mod m[2]),
x=b[3](mod m[3]),
~~~~~~
x=b[n](mod m[n])
m之间互质。
【输入格式】
一行一个整数 n(1<=n<=?)
下来n行每行两个整数b[i],m[i]。 1 ≤b[i],m[i] ≤ 10^9 
【输出格式】
一行一个整数x,无解输出"no solution!"
【样例输入】
3
3 5
2 3
2 7

【样例输出】
23

我们考虑一下答案有可能是什么?

M M M为所有 m m m的总乘,可以发现答案其实是可以加减 M M M的,所以我们就是把所有的方程组合并到一个模 M M M的方程里面。

那么我们只要找到一个一定是解的公式模 M M M就行了

就比如数据:

3
3 5
2 3
2 7

转换一下思路,我们需要找到一个数字 a a a,是 3 , 7 3,7 3,7的倍数,但是模 5 5 5 3 3 3,一个数字 b b b 5 , 7 5,7 5,7的倍数,模 3 3 3 2 2 2,一个数字 c c c 5 , 3 5,3 5,3的倍数,但是模 7 7 7 2 2 2,那么 a + b + c a+b+c a+b+c仔细想想就发现一定是其中的一组解了。

但是怎么找到 a , b , c a,b,c a,b,c呢,是 3 , 7 3,7 3,7的倍数,模 5 5 5 3 3 3的一个数字?其实很简单的一个做法就是先找到是 3 , 7 3,7 3,7的倍数,模 5 5 5 1 1 1的数字,然后乘以 3 3 3,对于前面的数字不就是在模 5 5 5意义下, l c m ( 3 , 7 ) lcm(3,7) lcm(3,7)(因为互质肯定为 3 ∗ 7 3*7 37)的逆元。

所以推到一下就会发现公式了,对于这道题目,我们设 M i − 1 M_i^{-1} Mi1表示模 m i m_i mi意义下 M i M_i Mi的逆元, M i = M m i M_i=\frac{M}{m_{i}} Mi=miM那么公式就是:

x = ( b 1 M 1 M 1 − 1 + . . . + b n M n M n − 1 ) % M x=(b_1M_1M_1^{-1}+...+b_nM_nM_n^{-1})\% M x=(b1M1M11+...+bnMnMn1)%M

这个东西最有用的地方在于它可以用于很多模数不为质数的算法中,可以把模数拆成各个不互质且更好计算的数字,最后合并。

没有代码QMQ。

扩展中国剩余定理exCRT

题目描述

【题意】 
同余方程是这样的:已知a,b,n,求x的最小正整数解,使得ax=b(mod m)
同余方程组是这样:也是求x的最小正整数解,但已知b数组和m数组的情况下,
x=b[1](mod m[1]),
x=b[2](mod m[2]),
x=b[3](mod m[3]),
~~~~~~
x=b[n](mod m[n])
【输入格式】
一行一个整数 n(1<=n<=?)
下来n行每行两个整数b[i],m[i]。 1 ≤b[i],m[i] ≤ 10^9 
【输出格式】
一行一个整数x,无解输出"no solution!"
【样例输入】
3
3 5
2 3
2 7

【样例输出】
23

这道题就十分的友(e)善(xin)了

现在我们可以列出 x = b 1 + m 1 y 1 , x = b 2 + m 2 y 2 . . . x=b_{1}+m_{1}y_{1},x=b_{2}+m_{2}y_{2}... x=b1+m1y1,x=b2+m2y2...

我们拿上面减下面的出: m 1 y 1 − m 2 y 2 = b 2 − b 1 m_{1}y_{1}-m_{2}y_{2}=b_{2}-b_{1} m1y1m2y2=b2b1也就是 a x + b y = c ax+by=c ax+by=c的形式。

那么我们写成 a x ′ + b y ′ = c , a = m 1 , b = m 2 ax'+by'=c,a=m_{1},b=m_{2} ax+by=c,a=m1,b=m2(这里是 − m 2 -m_{2} m2也无所谓,但是是正数后面算最小正整数比较方便。) , c = b 2 − b 1 , x ′ = y 1 , y ′ = y 2 ,c=b_{2}-b_{1},x'=y_{1},y'=y_{2} ,c=b2b1,x=y1,y=y2

然后解出 x ′ x' x,将 x ′ x' x带入 x = b 1 + a x ′ x=b_{1}+ax' x=b1+ax得出了 x x x的其中一个解,而且我们又知道 x x x要变只能加上或减去 b / ( a , b ) b/(a,b) b/(a,b),所以 x x x就只能加减 ( b / ( a , b ) ∗ a = [ a , b ] ) (b/(a,b)*a=[a,b]) (b/(a,b)a=[a,b]),所以我们又可以列出一个新的方程: x ≡ b 1 + a x ′ x≡b_{1}+ax' xb1+ax( m o d mod mod [ a , b ] [a,b] [a,b])(其中的 b 1 + a x ′ b_{1}+ax' b1+ax表示的是x的其中一组解),为了后面的方便,我们把这个方程代替为第二个方程,后面就是个二三做一次,三四做一次…

然后我们就可以拿这个方程与第三个方程做这种事,重复如此,最后的 b ? b_{?} b?就是答案,但是,别忘了是求最小整数解。


求最小整数解有两种方法:

我常用的方法:

开局直接把每个 b i b_{i} bi(不包括求出来的 b ? b_{?} b?)模 a i a_{i} ai,并且把每次求出的 x x x都做一次最小整数解,那么得出来的新的方程的 b ? b_{?} b?一定小于新的 m ? m_{?} m?,为什么, m ? m_{?} m? [ a , b ] [a,b] [a,b],而 b ? b_{?} b? b i + a x b_{i}+ax bi+ax得出的。(这里的 a a a表示 m i m_{i} mi b b b表示 m i + 1 m_{i+1} mi+1),我们知道 x ≤ b / ( a , b ) − 1 x≤b/(a,b)-1 xb/(a,b)1,那么 a x ≤ [ a , b ] − a ax≤[a,b]-a ax[a,b]a b i < a b_{i}<a bi<a,那么 a x + b i < [ a , b ] ax+b_{i}<[a,b] ax+bi<[a,b],所以 b ? b_{?} b?小于 m ? m_{?} m?,也就是不用模了。

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
LL  exgcd(LL  a,LL  b,LL  &x,LL  &y)
{
    
    
	if(a==0)
	{
    
    
		x=0;y=1;
		return  b;
	}
	else
	{
    
    
		LL  tx,ty;
		LL  d=exgcd(b%a,a,tx,ty);
		x=ty-(b/a)*tx;
		y=tx;
		return  d;
	}
}
LL  a1,b1;
bool  solve(LL  a2,LL  b2)
{
    
    
	LL  a=a1,b=a2,c=b2-b1,x,y;
	LL  d=exgcd(a,b,x,y);
	if(c%d!=0)return  false;
	else
	{
    
    
		c/=d;
		x*=c;
		LL  lca=b/d;
		x=((x%lca)+lca)%lca;//最小正整数解 
		b1=a1*x+b1;a1=a*b/d;//不用模 
		return  true;
	}
}
int  main()
{
    
    
	int  n;scanf("%d",&n);
	scanf("%lld%lld",&b1,&a1);b1%=a1;//开局模一模 
	for(int  i=2;i<=n;i++)
	{
    
    
		LL  a,b;scanf("%lld%lld",&b,&a);b%=a;//开局模一模 
		if(!solve(a,b))
		{
    
    
			printf("no solution!\n");
			return  0;
		}
	}
	printf("%lld\n",b1/*不用模*/);
	return  0;
}

但是后面,我又发现了一个不是很麻烦的方法,就是结尾模一下就行了。。。

//少了去正过程,中间会有许多数字变成负数
//而且更容易爆long long
#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
LL  exgcd(LL  a,LL  b,LL  &x,LL  &y)
{
    
    
	if(a==0)
	{
    
    
		x=0;y=1;
		return  b;
	}
	else
	{
    
    
		LL  tx,ty;
		LL  d=exgcd(b%a,a,tx,ty);
		x=ty-(b/a)*tx;
		y=tx;
		return  d;
	}
}
LL  zbs(LL  x){
    
    return  x<0?-x:x;}
LL  a1,b1;
bool  solve(LL  a2,LL  b2)
{
    
    
	LL  a=a1,b=a2,c=b2-b1,x,y;
	LL  d=exgcd(a,b,x,y);
	if(c%d!=0)return  false;
	else
	{
    
    
		c/=d;
		x*=c;//可能为负数 
		b1=a1*x+b1;a1=a*b/d;
		return  true;
	}
}
int  main()
{
    
    
	int  n;scanf("%d",&n);
	scanf("%lld%lld",&b1,&a1);
	for(int  i=2;i<=n;i++)
	{
    
    
		LL  a,b;scanf("%lld%lld",&b,&a);
		if(!solve(a,b))
		{
    
    
			printf("no solution!\n");
			return  0;
		}
	}
	LL  zhl=a1<0?-a1:a1;//绝对值 
	printf("%lld\n",(b1%zhl+zhl)%zhl/*尾巴模一模*/);
	return  0;
}

以上便是作者的心得。

一点想法

当然,如果每个 x x x前面也有系数 a i a_i ai怎么办?我们求出的值可能不能整除于 x x x的系数?

在一定有解的情况下,我们可以拆开成两个方程:一个是 x ≡ b m o d    m x≡b\mod m xbmodm

另外一个是 x ≡ 0 m o d    a x≡0\mod a x0moda

当然,这只是本蒟蒻的一点点假设,并没有实践过。

要注意的地方

这个EXCRT一定要特别注意正负问题,尤其是当数字特别大的时候,尽量做到每个地方都打模,才会比较保险。

猜你喜欢

转载自blog.csdn.net/zhangjianjunab/article/details/103936238