gcd运算严格满足结合率 即有 $\gcd(a,b,c) = \gcd(\gcd(a,b),c) = \gcd(a,\gcd(b,c))$
三者求最大公约数顺序任意排列
严格证明 d=gcd(a,b)意味着d是同时能整除a,b的最大整数,g=gcd(d,c),意味着g是能同时整除d,c的最大整数 因为g能整除d,d能整除a,b,故g能整除a,b,c,即g是a,b,c的公约数 反过来,我们取a,b,c的公约数k,k定然能被其最大公约数d整除,k同时又是d,c的因数,故又能被g整除,因此g定然是a,b,c中最大的公约数(a,b的公约数是gcd(a,b)的因数的证明在下)
本质推理是素因数分解
对于两个数而言,素因数分解 $a=p_{1}^{m1} \cdot p_{2}^{m2}…p_{n}^{mk}$ $b=p_{1}^{n1} \cdot p_{2}^{n2}…p_{nk}^{mk}$ 不难观察到,其最大公约数就是其素因数幂次的组合 即 最大公约数=$p_1^{\min(m_1,n_1)} p_2^{\min(m_2,n_2)} \cdots p_k^{\min(m_k,n_k)}$ 根据上述式子,我们可以得到a,b的其他任意公约数均可以整除最大公约数 故不管再多的数放在一起求gcd,都是满足结合律的。(故其实也可以直接从证明看出可结合性 本质是min取的过程中不在乎顺序如何)
exgcd模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x);//返回值是最大公约数 y -= a / b * x; return d; } /*void exgcd(ll a,ll b,ll &x,ll &y){ //更简洁的求解写法 if (b == 0) { x = 1,y = 0; return ;} exgcd(b,a%b,y,x); //x和y换位 y = y- a/b*x; }*/
返回的d即最终 $ax+by=gcd(a,b)$ 即:对于任意两个不全为零的整数 $a$ 和 $b$,一定存在整数 $x$ 和 $y$,使得 $ax+by=\gcd(a,b)$ 成立。同时通过值引用,$x,y$最终的值即$x,y$的最小特解。
用处:
1.求解二元一次不定方程 用于求解形如 $ax+by=c$ 的整数解。根据裴蜀定理的推论,这个方程有解的充要条件是 $\gcd(a,b)$ 能整除 $c$。通过 exgcd 求出 $ax+by=\gcd(a,b)$ 的一组解后,将其按比例扩大,即可得到原方程的一组特解$x_0,y_0$,进而可以推导出通解。 通解:$x=x_0 + k\cdot \frac{b}{gcd(a,b)}$ ,$y=y_0 - k\cdot \frac{a}{gcd(a,b)}$ (k是任意整数)
2.求解模综合乘法逆元 当要求解 $a$ 在模 $m$ 意义下的乘法逆元 $x$ 时,等价于求解同余方程 $ax\equiv 1\pmod m$。这个同余方程可以转化为不定方程 $ax+my=1$。只要 $\gcd(a,m)=1$(即 $a$ 和 $m$ 互质),就可以直接用 exgcd 求出 $x$ 的值。 这次的exgcd的结果是在模意义下的,故其是唯一的特解,需注意实际使用中通常还需要保证取模之后的结果为正数,故+m再取余以保证正数。
费马小定理解法
求乘法逆元同时也可使用费马小定理。注意:此时要求mod必须是质数 $ax \equiv 1\pmod m$ 有公式: x即a关于模m的乘法逆元
3.求解线性同余方程组(CRT 的扩展) 在处理扩展中国剩余定理(EXCRT)时,由于模数可能不互质,普通的 CRT 无法使用。此时需要两两合并同余方程,合并的过程本质上就是求解形如 $ax+by=c$ 的线性方程,强依赖 exgcd。
CRT求解模板 能力有限,模板转载自上述链接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using namespace std; typedef long long ll ; ll mod[15],yu[15],M = 1,ans;//mod[i]即为mi,yu[i]存放模后余数 void exGcd(ll a,ll b,ll &x,ll &y){ //求解 ax+by = gcd(a,b),注意是传引用 if (b == 0) { x = 1,y = 0; return ;} // b = 0时,a = gcd(原a,原b) exGcd(b,a%b,x,y); ll temX = x; x = y,y = temX - a/b * y; //x = y1, y = x1 - a/b * y1 (x1和y1是递归下一层返回后的x和y) } /*void exGcd(ll a,ll b,ll &x,ll &y){ //更简洁的写法 if (b == 0) { x = 1,y = 0; return ;} exGcd(b,a%b,y,x); //x和y换位 y = y- a/b*x; }*/ int main () { int n; cin>>n; //方程组数 for (int i = 1; i <= n ; ++i) { scanf("%ld %ld" ,&mod[i],&yu[i]); //模数和余数,模数互质 M*=mod[i]; } for (int i = 1; i <= n ; ++i) { ll Mi = M / mod[i],inv,y; // Mi为所有模数乘积除以第i个模数,inv为Mi在模mi意义下的逆元 exGcd(Mi, mod[i], inv, y); inv = (inv % mod[i]+mod[i]) % mod[i];//防止逆元求解过程中的负数出现 ans = (ans + yu[i] * Mi * inv) % M; } cout<< (ans + M) % M; //保证结果不出现负数 return 0; }
EXCRT求解模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 using namespace std; typedef long long ll; ll n,mod[100009],yu[100009]; //要用快速乘(龟速乘),防止爆long long ll qMul(ll a,ll b,ll mo){ ll an = 0; while (b) { if (b&1) an =(an+a) % mo; a = (a+a)%mo; b>>=1; }return an%mo; } //扩展欧几里得算法,返回gcd(a,b),并计算出ax+by = gcd(a,b)中的x和y ll exGcd(ll a,ll b,ll &x,ll &y){ if ( b == 0 ) { x = 1;y = 0; return a;} ll gcd = exGcd(b,a%b,y,x); //注意x和y的顺序 y = y - a/b*x; return gcd; } int main () { ios::sync_with_stdio(false );//加速cin和cout cin>>n; for (int i = 1;i <= n;i++) cin>>mod[i]>>yu[i]; ll ans = yu[1],M = mod[1] ,t,y; //ans表示前i-1个方程式的特解(余数),M为前i-1个方程式的模数的最小公倍数(i从2开始) for (int i = 2;i <= n;i++){ ll mi = mod[i],res = ((yu[i] - ans)%mi + mi)%mi; //res是等式的右边部分,不能出现负数 ll gcd = exGcd(M,mi,t,y); //求出gcd(mi,M) if(res % gcd != 0 ) { cout<<-1 <<endl;exit(0 ); } //如果等式右边不能整除gcd,方程组无解 t = qMul(t,res/gcd,mi); //求出t还要乘上倍数,注意是快速乘取模mi (对谁取模要分清) ans += t * M; //得到前i个方程的特解(余数) M = mi /gcd * M; //M等于lcm(M,mi),注意乘法要在除法后面做,否则会爆long long ans = (ans%M+M)%M; //让特解范围限定在0 ~(M-1 )内,防止会出现负数 } cout<<ans; return 0 ; }
放一道例题(hard) U628784 Laz玩游戏 key:ey33