【算法讲2:拓展欧几里得(简略讲)】求解 ax+by=c

欧几里得算法简介

  • 大部分参考与查阅:《初等数论及其基本应用》原书第六版。
  • 辗转相除法
    令整数 r 0 = a , r 1 = b r_0=a,r_1=b r0=ar1=b,满足 a ≥ b > 0 a\ge b>0 ab>0,连续做除法得到:
    r j = r j + 1 q j + 1 + r j + 2 ( j = 0   t o   n − 2 ) , r n + 1 = 0 r_j=r_{j+1}q_{j+1}+r_{j+2}(j=0\ to\ n-2),r_{n+1}=0 rj=rj+1qj+1+rj+2j=0 to n2rn+1=0
    那么 (a,b)= r n r_n rn
  • 例如对于 (252,198)的例子:
j r j r_j rj r j + 1 r_{j+1} rj+1 q j + 1 q_{j+1} qj+1 r j + 2 r_{j+2} rj+2
0252198154
119854336
25436118
3361820
  • 算法的时间复杂度(拉梅定律) O ( 5 log ⁡ 10 b ) O(5\log_{10} b) O(5log10b)

拓展欧几里得算法简介

  • 在欧几里得算法的基础上,我们记录出一些数值为了
  • 用线性组合来表示最大公因子
  • 比如, ( 252 , 198 ) = 18 (252,198)=18 (252,198)=18,但是我想知道 252 s + 198 t = 18 252s+198t=18 252s+198t=18,其中两个系数该怎么取。

【递归方法:倒序带入法】

见上倒数第二步, 18 = 54 − 1 ∗ 36 18=54-1*36 18=54136
前一步为 36 = 198 − 3 ∗ 54 36=198-3*54 36=198354
带入得到 18 = 54 − 1 ∗ ( 198 − 3 ∗ 54 ) = 4 ∗ 54 − 1 ∗ 198 18=54-1*(198-3*54)=4*54-1*198 18=541(198354)=4541198
⋯ \cdots

【算法描述】:
原理:更新 x 、 y x、y xy,使得 r j ∗ x + r j + 1 ∗ y = ( a , b ) r_j*x+r_{j+1}*y=(a,b) rjx+rj+1y=(a,b)恒成立。

步骤:首先跑一边欧几里得算法,接下来逆序计算。
首先, r n = ( a , b ) , r n + 1 = 0 r_n=(a,b),r_{n+1}=0 rn=(a,b),rn+1=0
对于特定的 j j j ,如果已经求得 ( a , b ) = s r j + t r j − 1 (a,b)=sr_j+tr_{j-1} (a,b)=srj+trj1
那么,因为 r j = r j + 1 q j + 1 + r j + 2 ( j = 0   t o   n − 2 ) , r n + 1 = 0 r_j=r_{j+1}q_{j+1}+r_{j+2}(j=0\ to\ n-2),r_{n+1}=0 rj=rj+1qj+1+rj+2j=0 to n2rn+1=0
等式变一变,我们有:
r j = r j − 2 − r j − 1 q j − 1 r_j=r_{j-2}-r_{j-1}q_{j-1} rj=rj2rj1qj1
带入得到:
( a , b ) = s ( r j − 2 − r j − 1 q j − 1 ) + t r j − 1 = ( t − s q j − 1 ) r j − 1 + s r j − 2 \begin{aligned}(a,b)&=s(r_{j-2}-r_{j-1}q_{j-1})+tr_{j-1}\\&=(t-sq_{j-1})r_{j-1}+sr_{j-2}\end{aligned} (a,b)=s(rj2rj1qj1)+trj1=(tsqj1)rj1+srj2
如果仔细看一下式子,便可以发现:

经过一次变换之后,
s s s变成 ( t − s q j − 1 ) (t-sq_{j-1}) (tsqj1)
t t t变成 s s s

经过多次变换之后,我们的 s 、 r s、r sr的系数就可以从 r n 、 r n + 1 r_n、r_{n+1} rnrn+1 变成 r 0 、 r 1 r_0、r_1 r0r1,也就是我们想要得到的线性组合的两个系数了。

【递归代码】

ll ex_gcd(ll a,ll b,ll& x,ll& y) { 
	if(b==0) {
		x=1;y=0;
		return a;
	}
	ll g=ex_gcd(b,a % b,x,y);
	ll tmp = x;x = y; y = tmp - a / b * y;
	return g; 
}

【递推方法:滚动变量记录法】

原理:我们保持 r j = s j a + t j b r_j=s_ja+t_jb rj=sja+tjb 其恒成立

首先,我们递归定义一下:

( a , b ) = s n a + t n b (a,b)=s_na+t_nb (a,b)=sna+tnb

s 0 = 1 , t 0 = 0 s_0=1,t_0=0 s0=1t0=0
s 1 = 0 , t 1 = 1 s_1=0,t_1=1 s1=0t1=1

∀   j ≥ 2 s j = s j − 2 − q j − 1 s j − 1 t j = t j − 2 − q j − 1 t j − 1 \forall\ j\ge 2\\s_j=s_{j-2}-q_{j-1}s_{j-1}\\t_j=t_{j-2}-q_{j-1}t_{j-1}  j2sj=sj2qj1sj1tj=tj2qj1tj1

证明(第二数归,略)

【递推代码】

ll ex_gcd2(ll a,ll b,ll& s1,ll &t1) {
    s1 = 0;
    t1 = 1;
    ll s2 = 1,t2 = 0;
    ll q;
    bool fir = true;
    while(b){
        if(!fir){
            ll n1 = s2 - q * s1;
            ll n2 = t2 - q * t1;
            s2 = s1;
            s1 = n1;
            t2 = t1;
            t1 = n2;
        }
        if(b)q = a / b;
        ll tmp = a;
        a = b;
        b = tmp % b;
        fir = false;
    }
    return a;
}

应用:如何解 ax+by=c

【题目介绍】
a 、 b 、 c a、b、c abc是已给出的整数。
我想求得一组整数解 x , y x,y x,y,满足 a ∗ x + b ∗ y = c a*x+b*y=c ax+by=c

  • 首先,我们得到 g = ( a , b ) g=(a,b) g=(a,b),并且容易知道 g ∣ ( a x + b y ) g|(ax+by) g(ax+by)
  • 换句话说,只有 g ∣ c g|c gc ,该方程才有解。
  • 并且,若 g ∣ c g|c gc,该方程有无穷多个解。

【常规解法】:

  1. 求出 g = ( a , b ) g=(a,b) g=(a,b)
  2. g ∤ c g\nmid c gc,则该方程无解,直接跳出。否则有无穷多个解。
  3. 我们先求出 a x + b y = g ax+by=g ax+by=g的解,然后方程两边同时乘以 c g , 即 可 得 到 解 \frac{c}{g},即可得到解 gc
  4. 解方程 a x + b y = g ax+by=g ax+by=g ,首先得求得该方程的一个解,即求出 ( a , b ) (a,b) (a,b) 的线性组合,拓展欧几里得求出的两个系数即为其方程的一个解 x 0 , y 0 x_0,y_0 x0,y0
  5. 方程 a x + b y = g ax+by=g ax+by=g 的通解为 :
    { x = x 0 + k b g y = y 0 − k a g k ∈ Z \begin{cases} x=x_0+k\frac{b}{g}\\ y=y_0-k\frac{a}{g}\\ \end{cases} \\k\in\mathbb{Z} {x=x0+kgby=y0kgakZ
  6. 方程 a x + b y = c ax+by=c ax+by=c 的通解为 :
    { x = x 0 c g + k b g y = y 0 c g − k a g k ∈ Z \begin{cases} x=x_0\frac{c}{g}+k\frac{b}{g}\\ y=y_0\frac{c}{g}-k\frac{a}{g}\\ \end{cases} \\k\in\mathbb{Z} {x=x0gc+kgby=y0gckgakZ

测试代码

可以看递归方法是否满足 r j ∗ x + r j + 1 ∗ y = ( a , b ) r_j*x+r_{j+1}*y=(a,b) rjx+rj+1y=(a,b)恒成立。
可以看递推方法是否满足 r j = s j a + t j b r_j=s_ja+t_jb rj=sja+tjb 其恒成立
可以看是否对于任意的整数 k k k 都满足 a x + b y = c ax+by=c ax+by=c
只要在宏定义上面修改A、B、C的值即可。(可以看看 C ∤ ( A , B ) C\nmid(A,B) C(A,B)会发生什么)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
#define ll long long
ll A = 252;
ll B = 198;
ll C = 36;
#define show(x) std::cerr << #x << " = " << x << std::endl
#define show2(x,y) std::cerr << #x << " = " << x << "   " << #y << " = " << y << std::endl
#define show3(x,y,z) std::cerr << #x << " = " << x << "   " << #y << " = " << y << "   " << #z << " = " << z << std::endl
#define show4(x,y,z,p) std::cerr << #x << " = " << x << "   " << #y << " = " << y << "   " << #z << " = " << z << "   " << #p << " = " << p << std::endl
ll ex_gcd(ll a,ll b,ll& x,ll& y) { if(b==0) { x=1;y=0;show2(x,y); return a; } ll g=ex_gcd(b,a%b,x,y); ll tmp=x; x=y; y=tmp-a/b*y; show3(x,y,a * x + b * y);return g; }

ll ex_gcd2(ll a,ll b,ll& s1,ll &t1) {
    s1 = 0;
    t1 = 1;
    ll s2 = 1,t2 = 0;
    ll q;
    bool fir = true;
    while(b){
        if(!fir){
            ll n1 = s2 - q * s1;
            ll n2 = t2 - q * t1;
            s2 = s1;
            s1 = n1;
            t2 = t1;
            t1 = n2;
            show2(s1,t1);
            show(s1 * A + t1 * B);
        }
        if(b)q = a / b;
        ll tmp = a;
        a = b;
        b = tmp % b;
        fir = false;
    }
    return a;
}

int main()
{
    ll x,y;
    cout << ex_gcd(A,B,x,y) << endl;
    show2(x,y);
    puts("----------");
    ll a,b;
    cout << ex_gcd2(A,B,a,b) << endl;
    show2(x,y);
    ll g = __gcd(A,B);
    for(int k = -5;k <= 5;++k){
        ll xx = x * C / g + k * B / g;
        ll yy = y * C / g - k * A / g;
        show2(xx,yy);
        cout << xx * A + yy * B << endl;
    }
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值