一篇文章讲明白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; }
注意,龟速乘不允许用于计数的那个乘数为负,所以一开始要特判一下