一篇文章讲明白gcd,exgcd,crt,excrt

 

0,取模与整除

这一部分讨论在c++中取模与整除的特性

取模操作,又称取余,在C++中的符号是% ,和乘除法属于同一运算优先级,模数不允许出现零
正数模正数结果是非负数,很好理解,但出现了负数怎么办?

#include <iostream>
using namespace std;
int main() {
cout << 8 % 3 << endl;
cout << 8 % -3 << endl;
cout << -8 % 3 << endl;
cout << -8 % -3 << endl;
return 0;
}
/*
2
2
-2
-2
*/

结论:取模的结果只与被模数的正负号有关

整除操作,是做除法后舍去余数得到的整数结果,依然需要讨论,出现了负数怎么办?

#include <iostream>
using namespace std;
int main() {
cout << 8 / 3 << endl;
cout << 8 / -3 << endl;
cout << -8 / 3 << endl;
cout << -8 / -3 << endl;
return 0;
}
/*
2
-2
-2
2
*/

结论:整除的结果和被除数与除数是否同号有关,商的绝对值必然等于绝对值之商,还有一个特性,就是商*除数+余数=被除数

进行整除运算时不要使用floor(),ceil()等容易导致浮点误差的函数.

一些常用的小技巧:

//向上取整(仅适用于正数)
int ceil_div(int a,int b){
return (a+b-1)/b;
}
//要求结果为正的取模
int mod_postive(int a,int b){
if(b<0)a=-a,b=-b;
return (a%b+b)%b;
}

1,gcd(Greatest Common Division,最大公约数)和lcm(Lowest Common Mutiple,最小公倍数)

使用辗转相除法(或称Euclid算法)计算

int gcd(int a,int b){
return b?a:gcd(b,a%b);
}

使用这种写法无需额外讨论正负号,a与b的大小的问题,但若a,b符号不同,计算出的结果正负号也难以寻找规律,不过显然有abs(gcd(a,b))=gcd(abs(a),abs(b))

没有必要讨论a,b中包含0的情况

辗转相除法的时间复杂度是O(log n),显然,每递归一次,max(a,b)至少减小一半,不过笔者认为这个上界还是太粗糙.

lcm的写法如下,注意,代码中的计算顺序是为了防止结果运算结果没有溢出,但中间过程的值却溢出.

int lcm(int a,int b){
return a/gcd(a,b)*b;
}

2,exgcd(Extend Grestest Common Division,扩展gcd)

定理:

若gcd(a,b)=g,则必然存在x,y,使得ax+by=g

辗转相除法的过程本身就是这个定理的构造性证明,因此要求得x,y,只需要保留在运算过程中被舍弃的信息

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

注意,解(x,y)不是唯一的,实际上有无穷多组,因为若(x,y)是一组解,(x+kb/g,y-ka/g)也是一组解,上述算法计算出的是x,y绝对值分别小于b/g,a/g的解的其中一组.

如果这时候a,b,x均大于0,g=1,那么a,x就互为模b意义下的逆元

因为不可避免地会出现负数(实际上a,b,x,y至少一个为负数),因此许多exgcd题目中正负号的讨论都会让人自闭,比如青蛙的约会,此时是降低思维难度,减少分类讨论情况数的最好方法是先透彻地理解题意,确定哪个参数有符号的限制;然后抓住辗转相除法的本质,对特解做出正确的恒等变换.

3,crt(Chinese Remain Theory,中国剩余定理)

中国剩余定理得名于中国古代的这样一个题目

有东西不知道多少个,三个三个数剩两个,五个五个数剩三个,七个七个数剩五个,问这堆东西有多少个

即,求解同余方程组

$$\left\{\begin{matrix}
x\mod 3=2\\
x\mod 5=3\\
x\mod 7=5\\
\end{matrix}\right.$$

显然解不是唯一的,因为若a满足条件,a+k(3*5*7)必定满足条件

求解这类问题就需要使用中国剩余定理

回过头来看前面的同余方程组,要求解x,只需要找到(y1,y2,y3),使得

$$\begin{matrix}
y_1\mod 3=1 & y_1\mod 5=0 & y_1\mod 7=0\\
y_2\mod 3=0 & y_2\mod 5=1 & y_2\mod 7=0\\
y_3\mod 3=0 & y_3\mod 5=0 & y_3\mod 7=1 \\
\end{matrix}$$

于是很显然地,$x=2y_1+3y_2+5y_3$,于是问题就变成了怎么求解$(y_1,y_2,y_3)$

我们用符号$rev(a,b)$来表示$a$(或者$a%b$)在取模$b$意义下的逆元,这个值容易通过对$a,b$做exgcd求得

那么便有了

$$\left\{\begin{matrix}
y_1=rev(5\times7,3)\times5\times7\\
y_2=rev(3\times7,5)\times3\times7\\
y_3=rev(3\times5,7)\times3\times5\\
\end{matrix}\right.$$

需要注意的是,模数必须两两互质,否则$rev(a,b)$未必存在,上述方法失效.

4,excrt(Extand Chinese Remain Theory,扩展中国剩余定理)

如果模数不互质,同余方程组未必有解,比如

$$\left\{\begin{matrix}x\mod 3=1\\
x\mod 9=8
\end{matrix}\right.$$

但未必无解,比如

$$\left\{\begin{matrix}x\mod 3=2\\
x\mod 6=2 \\x\mod 9=2 
\end{matrix}\right.$$

那么对于这种情况如何求解,或者证明无解呢?将同余方程组写成如下的形式

$$\left\{\begin{matrix}
x\mod m_1=a_1\\
x\mod m_2=a_2\\
x\mod m_3=a_3\\\cdots\end{matrix}\right.$$

将同余方程依次两两合并,比如第一个和第二个式子:

$$\left\{\begin{matrix}
x\mod m_1=a_1\\
x\mod m_2=a_2\\
\end{matrix}\right. \Rightarrow x\mod lcm(m_1,m_2)=a'$$

接下来的任务就是求解这个$a$',观察同余方程组的如下等价形式

$$\left\{\begin{matrix}
x\mod m_1=a_1  \\
x\mod m_2=a_2 
\end{matrix}\right. \Leftrightarrow
\left\{\begin{matrix}
x=a_1+y_1m_1\\
x=a_2+y_2m_2\\
\end{matrix}\right.\qquad \text{其中$y_1,y_2$为任意整数}$$

联立,得到

$$m_1y_1-m_2y_2=a_2-a_1$$

调用exgcd函数求解

$$g=exgcd(m_1,m_2,y_1',y_2')$$

注意,该函数不仅计算出了$g$,也计算出了一组$y_1',y_2'$

如果$g$不是$a_2-a_1$的因数,输出无解,否则

$$\left.\begin{matrix}
y_1=\frac{(a_2-a_1)y_1'}{g}\\
y_2=\frac{(a_2-a_1)y_2'}{g}
\end{matrix}\right. $$

所以能够得到等式

$$x=(a_1+\frac{m_1}{g}\cdot(a2-a1)\cdot y_1)+y\cdot lcm(m_1,m_2)\qquad \text{其中$y$为任意整数}$$

记$$a'=a_1+\frac{m_1}{g}\cdot(a2-a1)\cdot y_1$$,

那么我们完成了前两个式子的合并过程,得到

$$x\mod lcm(m_1,m_2)=a'$$

,如果一直到所有式子都被合并且未出现无解的情况,最后得到的$a'$就是特解,$a'+lcm(m_1,m_2\cdots)$就是通解

5,龟速乘

在计算excrt的时候可能会出现两个乘数和结果模数都在long long范围内,但是没取模的结果却溢出的尴尬情况,为了防止溢出或误差,应当用类似快速幂的方式求解整数乘法

LL turtle_mult(LL base,LL n,LL mod){
if(n<0){
base=-base;
n=-n;
}
LL ans=0;
while(n){
if(n&1)ans=(ans+base)%mod;
base=base+base%mod;
n>>=1;
}
return ans;
}

注意,龟速乘不允许用于计数的那个乘数为负,所以一开始要特判一下

posted @ 2021-09-26 21:56  Isakovsky  阅读(341)  评论(0编辑  收藏  举报