L1 前论
1. 欧几里得算法
欧几里德算法又称辗转相除法,用于计算两个整数
a
,
b
a,b
a,b的最大公约数。
g
c
d
gcd
gcd函数的基本性质:
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
)
=
g
c
d
(
−
a
,
b
)
=
g
c
d
(
∣
a
∣
,
∣
b
∣
)
gcd(a,b)=gcd(b,a)=gcd(-a,b)=gcd(|a|,|b|)
gcd(a,b)=gcd(b,a)=gcd(−a,b)=gcd(∣a∣,∣b∣)
公式表述
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,amodb)
L2 扩展算法
对于不完全为 0 的非负整数 a , b , g c d ( a , b ) a,b,gcd(a,b) a,b,gcd(a,b)表示 a , b a,b a,b 的最大公约数,必然存在整数对 x , y x,y x,y,使得 g c d ( a , b ) = a x + b y gcd(a,b)=ax+by gcd(a,b)=ax+by。
int gcd(int a,int b,int &x,int &y){
if (b==0){
x=1,y=0;
return a;
}
int q=gcd(b,a%b,y,x);
y-=a/b*x;
return q;
}
和 g c d ( a , b ) gcd(a,b) gcd(a,b)函数一样,扩展 g c d gcd gcd也是通过递归的思想来实现。在 g c d ( a , b , x , y ) gcd(a,b,x,y) gcd(a,b,x,y)的 b ! = 0 b!=0 b!=0时,会运算函数 g c d ( b , a % b , y , x ) gcd(b,a\%b,y,x) gcd(b,a%b,y,x),直到b=0时函数返回此时a的值
对于x,y的赋值才是这个函数的精髓。
L3 算法证明
不妨先设a>b
在b=0时,显然gcd(a,b)=a。此时 x=1,y=0;
a>b>0 时
设
ax1+ by1= gcd(a,b); //原函数gcd(a,b,x,y)
bx2+ (a mod b)y2= gcd(b,a mod b); //第一次递归的函数gcd(b,a%b,y,x)
欧几里德原理有 gcd(a,b) = gcd(b,a mod b);
∴ax1+ by1= bx2+ (a mod b)y2
而对于mod运算,a-[a/b]*b即为a mod b。[a/b]代表取小于a/b的最大整数
mod运算代入原等式得:ax1+ by1= bx2+ (a - [a / b] * b)y2=ay2+ bx2- [a / b] * by2
化简得:ax1+ by1 == ay2+ b(x2- [a / b] *y2);
此时便有恒等式:x1=y2; y1=x2- [a / b] *y2;
已经很明显了,x1,y1 的值基于 x2,y2,当递归到b==0时,对x,y赋值为1,0,然后一层一层用恒等式改变x和y的值
L4 算法讲解
和欧几里得一样,扩展欧几里得函数返回的是gcd(a,b),此时的x和y带入原方程ax+by等的是gcd(a,b)
要想等的是原方程的c,那么gcd(a,b)要变成c,也就是如下方程:
a
∗
x
∗
c
g
c
d
(
a
,
b
)
+
b
∗
y
∗
c
g
c
d
(
a
,
b
)
=
g
c
d
(
a
,
b
)
∗
c
g
c
d
(
a
,
b
)
a*x*\dfrac{c}{gcd(a,b)}+b*y*\dfrac{c}{gcd(a,b)}=gcd(a,b)*\dfrac{c}{gcd(a,b)}
a∗x∗gcd(a,b)c+b∗y∗gcd(a,b)c=gcd(a,b)∗gcd(a,b)c
所以当c不能整除gcd(a,b)时,
x
∗
c
g
c
d
(
a
,
b
)
x*\dfrac{c}{gcd(a,b)}
x∗gcd(a,b)c在数学的角度并不是一个整数,即,
a
∗
x
+
b
∗
x
=
c
a*x+b*x=c
a∗x+b∗x=c 无整数解
L5 解的情况
可知,如果
a
x
+
b
y
=
c
ax+by=c
ax+by=c有解,那么一定是无限组解
设有任意的一组解为 ( x , y ) (x,y) (x,y),那么 ( x + k ∗ b / g c d ( a , b ) , y − k ∗ a / g c d ( a , b ) ) (x+k*b/gcd(a,b),y-k*a/gcd(a,b)) (x+k∗b/gcd(a,b),y−k∗a/gcd(a,b))也是这个方程的解,这个便是我们求某种特殊解的前提
假设题目要求的是x的最小非负整数解,那么我们用扩展欧几里得求出来的x,先乘上
c
g
c
d
(
a
,
b
)
\dfrac{c}{gcd(a,b)}
gcd(a,b)c使之变成ax+by=c的解,再通过一下操作拉到
[
0
,
b
/
g
c
d
(
a
,
b
)
]
[0,b/gcd(a,b)]
[0,b/gcd(a,b)]范围
令
d
=
b
/
g
c
d
(
a
,
b
)
x
=
(
x
%
d
+
d
)
%
d
令d=b/gcd(a,b)\\x=(x\%d+d)\%d
令d=b/gcd(a,b)x=(x%d+d)%d
当然如果求的是最小正整数解,只需要在x为0的时候变成b/gcd(a,b)就可以了
L5 模板(x为最小非负整数解)
/*
* Author : Jk_Chen
* Date : 2019-08-21-10.26.44
*/
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define per(i,a,b) for(int i=(int)(a);i>=(int)(b);i--)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pill pair<int, int>
#define fi first
#define se second
#define debug(x) cerr<<#x<<" = "<<x<<'\n';
const LL mod=1e9+7;
const int maxn=1e5+9;
LL rd(){ LL ans=0; char last=' ',ch=getchar();
while(!(ch>='0' && ch<='9'))last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
/*_________________________________________________________head*/
LL Ex_gcd(LL a,LL b,LL &x,LL &y){
if(!b){
y=0,x=1;
return a;
}
LL ans=Ex_gcd(b,a%b,y,x);
y-=a/b*x;
return ans;
}
LL MinNotNeg(LL a,LL b,LL c){
if(!a&&!b){
return -(c!=0);
}
LL x,y;
LL G=Ex_gcd(a,b,x,y);
if(c%G)return -1;
x*=c/G; y*=c/G;
b=abs(b/G);
return (x%b+b)%b;
}
int main(){
cout<<MinNotNeg(5,7,12)<<endl;
return 0;
}
求逆元
青蛙的约会
链接:zjnu 1439
题意大致为:
在一首位相接的长为L的数轴上,A从坐标x出发,每次往正方向走m,B从坐标y出发,每次往正方向走n,问能不能在某一次跳跃后落在同一点,如果能,输出跳跃次数,如果不能,输出“Impossible”。
分析:
经过t次跳跃,A坐标为
(
x
+
(
t
∗
m
)
)
(x+(t*m))%L
(x+(t∗m)),B坐标为
(
y
+
(
t
∗
n
)
)
(y+(t*n))%L
(y+(t∗n)),当存在t使两者相同时,此t便是ans。
(
x
+
t
∗
m
)
%
L
−
(
y
+
t
∗
n
)
%
L
=
0
(x+t*m)\%L - (y+t*n)\%L = 0
(x+t∗m)%L−(y+t∗n)%L=0
(
(
x
+
t
∗
m
)
−
(
y
+
t
∗
n
)
)
%
L
=
0
( (x+t*m)-(y+t*n) )\%L = 0
((x+t∗m)−(y+t∗n))%L=0
(
x
−
y
)
+
(
m
−
n
)
∗
t
=
k
∗
L
(x-y)+(m-n)*t = k*L
(x−y)+(m−n)∗t=k∗L
L
∗
k
+
(
n
−
m
)
∗
t
=
x
−
y
L*k+(n-m)*t=x-y
L∗k+(n−m)∗t=x−y
a
∗
X
′
+
b
∗
Y
′
=
c
a*X'+b*Y'=c
a∗X′+b∗Y′=c
(
L
=
a
;
n
−
m
=
b
;
x
−
y
=
c
;
X
′
=
k
;
Y
′
=
t
)
(L=a ; n-m=b ; x-y=c ; X'=k ; Y'=t)
(L=a;n−m=b;x−y=c;X′=k;Y′=t)
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define D long long
using namespace std;
D gcd(D a,D b,D &x,D &y){
if (b==0){
x=1,y=0;
return a;
}
D q=gcd(b,a%b,y,x);
y-=a/b*x;
return q;
}
int main()
{
D x,y,n,m,L;while(cin>>x>>y>>m>>n>>L){
D a,b,c;a=L;b=n-m;c=x-y;
D X,Y;
//a*X+b*Y=c
D mid=gcd(a,b,X,Y);//此时赋值后的的Y是 Y'*gcd(a,b)/c (Y'是最后的解)
//cout<<X<<' '<<Y<<endl;
D ans=Y*c/mid;
D mul=a/mid;//Y'的可变化幅度,(二元一次一定有多个解)
mul=mul<0?-mul:mul;//mul为负数时,答案可能为负
ans=(ans%mul+mul)%mul;//把ans拉回了[0,mul)
if(c%mid!=0||n==m)cout<<"Impossible"<<endl;
else cout<<ans<<endl;
}
}