1.欧几里得算法(辗转相除法)

递归写法:
int gcd(int a,int b)
{
if(b == 0) return a;
else return gcd(b,a%b);
}
更简洁的写法:
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}

扩展:求最大公倍数 lcm

lcm(a,b) = (a*b)/gcd(a,b);

//为了防止a*b超long long 溢出

//所以建议这样写

lcm(a,b) = a/gcd(a,b)*b;

 

两个公式的推广:

//这两个公式利用gcd(a/c,b/c) = 1   (注:c = gcd(a,b))可以推理证明

gcd(k*a,k*b) = k*gcd(a,b);

lcm(k*a,k*b) = k*lcm(a,b);

 

//这个公式可以把左边的分式通分来推理证明

lcm(s/a,s/b) = s/gcd(a,b);

2.引入求余概念

求余:

a%b = a mod b = a-a/b*b   (一般题目中b 都是 正整数)

注:当a为负整数时, 应保证取模结果为正,所以 (a%b+b)%b

 

同余:

定义:给定一个​正整数​​m,如果两个​​整数​​​a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对​​模​​m同余,记作a≡b(mod m)。对模m同余是整数的一个等价关系

a≡b(mod m)  称为 a与b对mod m 同余

可推理的结论: a % b = m,因为(a – b) mod m = 0

例如:26≡2(mod 12) , (26-2) mod12 = 0

 

模运算公式:

(a +  b) % p = (a%p +  b%p) %p  (对)

(a  -  b) % p = (a%p  -  b%p) %p  (对)

(a  *  b) % p = (a%p *  b%p) %p  (对)

(a  /  b) % p = (a%p  /  b%p) %p  (错)   ----- 为什么是错的呢( •̀∀•́ )?

证明一个公式是对的难,但是举一个反例就行! 吼吼(~y▽~)~

Ex:(100/50)%20 = 2    ≠   (100%20) / (50%20) %20 = 0

那么我们要怎么解决酱紫的问题呢?

此时的逆元(暗中观察~

3.逆元 (数论中的“倒数”)

定义:只有a和p互质的时候,a才有关于p的逆元,所以当有多个p和a互质时,所求的a关于p的逆元也是不同的。

a*x ≡ 1 (mod p)  其中x 叫做 a的关于p的逆元,记为:inv(a) = x

所以a * inv(a) ≡ 1 (mod p)

 

举个栗子:

a*x = 1那么x是a的倒数,x = 1/a

但是a如果不是1,那么x就是小数

那数论中,大部分情况都有求余,所以现在问题变了

a*x ≡ 1 (mod p)

那么x一定等于1/a吗

不一定

所以这时候,我们就把x看成a的倒数,只不过加了一个求余条件,所以x叫做    a关于p的逆元

比如2 * 3 % 5 = 1,那么3就是2关于5的逆元,或者说2和3关于5互为逆元

这里3的效果是不是跟1/2的效果一样,所以才叫数论倒数

 

那么我们上边错误的(a  /  b) % p = (a%p  /  b%p) %p 就可以 利用逆元

改写为:(a  /  b) % p =  (a * inv(b) ) % p = (a % p * inv(b) % p) % p

所以我们把错误的除法模运算规则改为了正确的乘法模运算规则

 

求逆元:

  1. 费马小定理

定理内容:a^(p-1) ≡1 (mod p)

求逆元:两边同除以a 得到 a^(p-2) ≡1/a (mod p)

什么(,,• ₃ •,,),这可是数论,还敢写1/a 应该是a^(p-2) ≡ inv(a) (mod p)

所以inv(a) = a^(p-2) (mod p).

 

使用条件: p是质数,并且gcd(a,p) = 1

typedef long long ll;
ll qpow(ll a,ll b,ll p)
{
ll tmp = 1;
while(b)
{
if(1&b) tmp = (tmp*a)%p;
a = (a*a)%p;
b>>=1;
}
return tmp;
}
ll inv(ll a,ll p) //费马小定理求逆元
{
return qpow(a,p-2,p);
}

基本算法:对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by。

证明:设 a>b。

  1,显然当 b=0,gcd(a,b)=a。此时 x=1,y=0;

  2,ab!=0 时

  设 ax1+by1=gcd(a,b);

  bx2+(a mod b)y2=gcd(b,a mod b);

  根据朴素的欧几里德原理有 gcd(a,b)=gcd(b,a mod b);

  则:ax1+by1=bx2+(a mod b)y2;

  即:ax1+by1=bx2+(a-(a/b)*b)y2=ay2+bx2-(a/b)*by2;

  根据恒等定理得:x1=y2; y1=x2-(a/b)*y2;

     这样我们就得到了求解 x1,y1 的方法:x1,y1 的值基于 x2,y2.

   上面的思想是以递归定义的,因为 gcd 不断的递归求解一定会有个时候 b=0,所以递归可以结束。

 

int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
return r;
}

扩展欧几里德算法的应用主要有以下三方面:

(1)求解不定方程;

(2)求解模线性方程(线性同余方程);

(3)求解模的逆元;

(1)使用扩展欧几里德算法解决不定方程的办法:

  对于不定整数方程pa+qb=c,若 c mod Gcd(p, q)=0,则该方程存在整数解,否则不存在整数解。
  上面已经列出找一个整数解的方法,在找到p * a+q * b = Gcd(p, q)的一组解p0,q0后,p * a+q * b = Gcd(p, q)的其他整数解满足:
  p = p0 + b/Gcd(p, q) * t 
  q = q0 - a/Gcd(p, q) * t(其中t为任意整数)
  至于pa+qb=c的整数解,只需将p * a+q * b = Gcd(p, q)的每个解乘上 c/Gcd(p, q) 即可。

  在找到p * a+q * b = Gcd(a, b)的一组解p0,q0后,应该是得到p * a+q * b = c的一组解p1 = p0*(c/Gcd(a,b)),q1 = q0*(c/Gcd(a,b)),

  p * a+q * b = c的其他整数解满足:

  p = p1 + b/Gcd(a, b) * t

  q = q1 - a/Gcd(a, b) * t(其中t为任意整数)

  p 、q就是p * a+q * b = c的所有整数解。


用扩展欧几里得算法解不定方程ax+by=c;

代码如下:

bool linear_equation(int a,int b,int c,int &x,int &y)
{
int d=exgcd(a,b,x,y);
if(c%d)
return false;
int k=c/d;
x*=k; y*=k; //求得的只是其中一组解
return true;
}

(2)用扩展欧几里德算法求解模线性方程的方法:

    同余方程 ax≡b (mod n)对于未知数 x 有解,当且仅当 gcd(a,n) | b。且方程有解时,方程有 gcd(a,n) 个解。

    求解方程 ax≡b (mod n) 相当于求解方程 ax+ ny= b, (x, y为整数)

    设 d= gcd(a,n),假如整数 x 和 y,满足 d= ax+ ny(用扩展欧几里德得出)。如果 d| b,则方程

    a* x0+ n* y0= d, 方程两边乘以 b/ d,(因为 d|b,所以能够整除),得到 a* x0* b/ d+ n* y0* b/ d= b。
    所以 x= x0* b/ d,y= y0* b/ d 为 ax+ ny= b 的一个解,所以 x= x0* b/ d 为 ax= b (mod n ) 的解。

    ax≡b (mod n)的一个解为 x0= x* (b/ d ) mod n,且方程的 d 个解分别为 xi= (x0+ i* (n/ d ))mod n {i= 0... d-1}。

    设ans=x*(b/d),s=n/d;

    方程ax≡b (mod n)的最小整数解为:(ans%s+s)%s;

    相关证明:

    证明方程有一解是: x0 = x'(b/d) mod n;
    由 a*x0 = a*x'(b/d) (mod n)
         a*x0 = d (b/d) (mod n)   (由于 ax' = d (mod n))
                 = b (mod n)

    证明方程有d个解: xi = x0 + i*(n/d)  (mod n);
    由 a*xi (mod n) = a * (x0 + i*(n/d)) (mod n)
                             = (a*x0+a*i*(n/d)) (mod n)
                             = a * x0 (mod n)             (由于 d | a)
                             = b

     

首先看一个简单的例子:

5x=4(mod3)

解得x = 2,5,8,11,14.......

由此可以发现一个规律,就是解的间隔是3.

那么这个解的间隔是怎么决定的呢?

如果可以设法找到第一个解,并且求出解之间的间隔,那么就可以求出模的线性方程的解集了.

我们设解之间的间隔为dx.

那么有

a*x = b(mod n);

a*(x+dx) = b(mod n);

两式相减,得到:

a*dx(mod n)= 0;

也就是说a*dx就是a的倍数,同时也是n的倍数,即a*dx是a 和 n的公倍数.为了求出dx,我们应该求出a 和 n的最小公倍数,此时对应的dx是最小的.

设a 和 n的最大公约数为d,那么a 和 n 的最小公倍数为(a*n)/d.

即a*dx = a*n/d;

所以dx = n/d.

因此解之间的间隔就求出来了.

    代码如下:

bool modular_linear_equation(int a,int b,int n)
{
int x,y,x0,i;
int d=exgcd(a,n,x,y);
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;
}

(3)用欧几里德算法求模的逆元:

       同余方程ax≡b (mod n),如果 gcd(a,n)== 1,则方程只有唯一解。

      在这种情况下,如果 b== 1,同余方程就是 ax=1 (mod n ),gcd(a,n)= 1。

      这时称求出的 x 为 a 的对模 n 乘法的逆元。

      对于同余方程 ax= 1(mod n ), gcd(a,n)= 1 的求解就是求解方程

      ax+ ny= 1,x, y 为整数。这个可用扩展欧几里德算法求出,原同余方程的唯一解就是用扩展欧几里德算法得出的 x 。