exgcd

exgcd学习笔记

exgcd的应用

exgcd即扩展欧几里得算法,用来解决形如 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b) ( a , b a,b a,b 为常数) 的方程的一组整数解 。

exgcd推导过程

b = 0 b=0 b=0 时,容易发现方程的解即为 x = 1 , y = 0 x=1,y=0 x=1,y=0

b ≠ 0 b\neq0 b=0 时,考虑到 g c d ( a , b ) = g c d ( b , a   m o d   b ) gcd(a,b)=gcd(b,a \ mod \ b) gcd(a,b)=gcd(b,a mod b) ,假设函数 e x g c d ( a , b , x , y ) exgcd(a,b,x,y) exgcd(a,b,x,y) 中的 x , y x,y x,y为解

那么 e x g c d ( a , b , x , y ) = e x g c d ( b , a   m o d   b , x 0 , y 0 ) exgcd(a,b,x,y)=exgcd(b,a \ mod \ b,x_0,y_0) exgcd(a,b,x,y)=exgcd(b,a mod b,x0,y0)

解这个方程

a x + b y = b x 0 + ( a   m o d   b )   y 0 ax+by=bx_0+(a \ mod \ b ) \ y_0 ax+by=bx0+(a mod b) y0

a   m o d   b = a − ⌊ a b ⌋ b a \ mod \ b= a-\lfloor \frac{a}{b} \rfloor b a mod b=abab

所以 a x + b y = b x 0 + a y 0 − ⌊ a b ⌋ b y 0 ax+by=bx_0+ay_0- \lfloor \frac{a}{b} \rfloor by_0 ax+by=bx0+ay0baby0

a x + b y = a y 0 + b ( x 0 − ⌊ a b ⌋ y 0 ) ax+by=ay_0+b(x_0-\lfloor \frac{a}{b} \rfloor y_0) ax+by=ay0+b(x0bay0)

则方程 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b) 的一个解为 x = y 0 , y = x 0 − ⌊ a b ⌋ y 0 x=y_0,y=x_0- \lfloor \frac{a}{b} \rfloor y_0 x=y0,y=x0bay0

通俗一点的代码

int exgcd(int a,int b,int &x,int &y){
    if(!b){
        x=1;y=0;
        return a;
    }
    int x0,y0;
    int d=exgcd(b,a%b,x0,y0);//先求解x0,y0
    x=y0,y=x0-a/b*y0;//给目前的解赋值
    return d;
}

简洁的代码

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);//先求解gcd(b,a%b)的解
    y-=a/b*x;
    return d;
}

返回值为 g c d ( a , b ) gcd(a,b) gcd(a,b)

exgcd的通解

假设存在 x 0 = x + i , y 0 x_0=x+i,y_0 x0=x+i,y0 也满足 a x 0 + b y 0 = g c d ( a , b ) = a x + b y ax_0+by_0=gcd(a,b)=ax+by ax0+by0=gcd(a,b)=ax+by

那么 a x + a i + b y 0 = a x + b y ax+ai+by_0=ax+by ax+ai+by0=ax+by

化简得 y 0 = b y − a i b y_0=\frac{by-ai}{b} y0=bbyai

y 0 = y − a i b y_0=y-\frac{ai}{b} y0=ybai

需要满足 y 0 y_0 y0 为整数 即需找出最小的 i i i 满足 a ∗ i   m o d   b = 0 a*i \ mod \ b=0 ai mod b=0

首先 i i i b b b 一定是可行的 考虑怎么缩小 i i i

容易发现 a ∗ i a*i ai 一定会包含 a a a 的因数 那么我们就可以将 i i i a a a 的因数删去

i = b / g c d ( a , b ) i=b/gcd(a,b) i=b/gcd(a,b)

那么

x 0 = x + i = x + b g c d ( a , b ) y 0 = y − a i b = y − a g c d ( a , b ) x_0=x+i=x+\frac{b}{gcd(a,b)} \\ y_0=y-\frac{ai}{b}=y-\frac{a}{gcd(a,b)} x0=x+i=x+gcd(a,b)by0=ybai=ygcd(a,b)a

{ x 0 = x + b g c d ( a , b ) y 0 = y − a g c d ( a , b ) \begin{cases} x_0=x+\frac{b}{gcd(a,b)} \\ y_0=y-\frac{a}{gcd(a,b)} \end{cases} {x0=x+gcd(a,b)by0=ygcd(a,b)a

当然 这样表示不合理 我们用一般用 x 0 , y 0 x_0,y_0 x0,y0表示特解 x , y x,y x,y 表示通解。

于是方程 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b) 的通解即为
{ x = x 0 + k b g c d ( a , b ) y = y 0 − k a g c d ( a , b ) , k ∈ Z \begin{cases} x=x_0+k \frac{b}{gcd(a,b)} \\ y=y_0-k \frac{a}{gcd(a,b)} \end{cases} , k\in Z {x=x0+kgcd(a,b)by=y0kgcd(a,b)a,kZ

exgcd的最小正整数解

d x d_x dx 代表通解中 x x x 的变化量, d y d_y dy 代表通解中 y y y 的变化量

k x k_x kx代表将特解 x 0 x_0 x0 变为最小正整数解的 k k k

k y k_y ky代表将特解 y 0 y_0 y0 变为最小正整数解的 k k k

需要注意通解中 x , y x,y x,y 变化量符号是相反的

那么满足 x , y x,y x,y 均为正整数的 k k k 的区间即为 [ k x , k y ] [k_x,k_y] [kx,ky]

if(x<=0){
    Kx+=-x/dx+1; //向下取整+1
}
else {
    Kx-=(x-1)/dx; //减到1(因为x>0) 向下取整
}
if(y<=0){
    Ky-=-y/dy+1;
}
else {
    Ky+=(y-1)/dy;
}
看这道题目 P5656

题目描述

给定不定方程

a x + b y = c ax+by=c ax+by=c

若该方程无整数解,输出 − 1 -1 1
若该方程有整数解,且有正整数解,则输出其正整数解的数量,所有正整数解中 x x x 的最小值,所有正整数解中 y y y 的最小值,所有正整数解中 x x x 的最大值,以及所有正整数解中 y y y 的最大值。
若方程有整数解,但没有正整数解,你需要输出所有整数解 x x x 的最小正整数值, y y y 的最小正整数值。

正整数解即为 x , y x, y x,y 均为正整数的解, 0 \boldsymbol{0} 0 不是正整数
整数解即为 x , y x,y x,y 均为整数的解。
x x x 的最小正整数值即所有 x x x 为正整数的整数解中 x x x 的最小值, y y y 同理。

输入格式

第一行一个正整数 T T T,代表数据组数。

接下来 T T T 行,每行三个由空格隔开的正整数 a , b , c a, b, c a,b,c

输出格式

T T T 行。

若该行对应的询问无整数解,一个数字 − 1 -1 1
若该行对应的询问有整数解但无正整数解,包含 2 2 2 个由空格隔开的数字,依次代表整数解中, x x x 的最小正整数值, y y y 的最小正整数值。
否则包含 5 5 5 个由空格隔开的数字,依次代表正整数解的数量,正整数解中, x x x 的最小值, y y y 的最小值, x x x 的最大值, y y y 的最大值。

代码(开long long)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
//#include<random>
//#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
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 solve(){
    int a,b,c;
    cin>>a>>b>>c;
    int g=__gcd(a,b);
    if(c%g){
        cout<<-1<<'\n';
        return ;
    }
    int x,y;
    exgcd(a,b,x,y);
    x*=c/g,y*=c/g;
    int dx=b/g,dy=a/g;
    int Kx=0,Ky=0;
    if(x<=0){
        Kx+=-x/dx+1;
    }
    else {
        Kx-=(x-1)/dx;
    }
    if(y<=0){
        Ky-=-y/dy+1;
    }
    else {
        Ky+=(y-1)/dy;
    }
    if(Ky-Kx+1<=0){
        cout<<x+Kx*dx<<' '<<y-Ky*dy<<'\n';
    }
    else {
        cout<<Ky-Kx+1<<' ';
        cout<<x+Kx*dx<<' '<<y-Ky*dy<<' ';
        cout<<x+Ky*dx<<' '<<y-Kx*dy<<'\n';
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)solve();
    return 0;
}
  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值