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=a−⌊ba⌋b
所以 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+ay0−⌊ba⌋by0
即 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(x0−⌊ba⌋y0)
则方程 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=x0−⌊ba⌋y0
通俗一点的代码
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=bby−ai
即 y 0 = y − a i b y_0=y-\frac{ai}{b} y0=y−bai
需要满足 y 0 y_0 y0 为整数 即需找出最小的 i i i 满足 a ∗ i m o d b = 0 a*i \ mod \ b=0 a∗i mod b=0
首先 i i i 为 b b b 一定是可行的 考虑怎么缩小 i i i
容易发现 a ∗ i a*i a∗i 一定会包含 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=y−bai=y−gcd(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=y−gcd(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=y0−kgcd(a,b)a,k∈Z
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;
}