过一段时间不用拓展欧几里得就把代码忘了,这是没有深入理解的恶果啊。写下一文,回忆回忆基础而重要的拓展欧几里得
拓展欧几里得函数 ex_gcd() 可以用于求解逆元,不定方程,同余式(随便也能把模与被模数的最大公约数求出来)。
贴上代码:
void ex_gcd(LL a,LL b,LL &d,LL &x,LL &y){
if(b==0){
d=a; x=1; y=0;
return ;
}
ex_gcd(b,a%b,d,x,y);
LL t=x;
x=y;
y=t-a/b*y;
}
工作原理:
例如:number=225 mod=21
辗转相减:
225-21*10=15
21-15*1=6
15-6*2=3
6-3*2=0
最大公约数即是: 3
求解225模21的逆元:
当ex_gcd()递归终止时,两个数字的最大公约数被找到,两个数字化简后进行下面的回溯运算:
X是上一次的Y,Y是上一次的X减去(a/b)和上一次Y的乘积。
关于实验的代码:
ex_gcd()在求解逆元和方面就不说了,下面聊一聊它在不定方程上的应用:
关于不定方程:
当然,实际写程序中更方便的做法是: 在用拓展欧几里得时直接带入x0,y0就行,最后X0=c/d*x0 Y0=c/d*y0
相关例题:
SGU 106 The equation
http://acm.sgu.ru/problem.php?contest=0&problem=106
X是上一次的Y,Y是上一次的X减去(a/b)和上一次Y的乘积。
得到的x就是相关逆元(两个数字互质时的逆元)
75*3=225%7=1
中间值:
18435915192532888 225 21
18435915192532888 21 15
18435915192532888 15 6
18435915192532888 6 3
18435915192532888 3 0
3 0 1 6 3
3 1 -2 15 6
3 -2 3 21 15
3 3 -32 225 21
Process returned 0 (0x0) execution time : 0.391 s
Press any key to continue.
关于实验的代码:
void ex_gcd(LL a,LL b,LL &d,LL &x,LL &y){
cout<<d<<" "<<a<<" "<<b<<endl;
if(b==0){
d=a; x=1; y=0;
return ;
}
ex_gcd(b,a%b,d,x,y);
LL temp=x;
x=y;
y=temp-a/b*y;
cout<<d<<" "<<x<<" "<<y<<" "<<a<<" "<<b<<endl;
}
int main()
{
LL d,x,y;
ex_gcd(225,21,d,x,y);
return 0;
}
ex_gcd()在求解逆元和方面就不说了,下面聊一聊它在不定方程上的应用:
关于不定方程:
当然,实际写程序中更方便的做法是: 在用拓展欧几里得时直接带入x0,y0就行,最后X0=c/d*x0 Y0=c/d*y0
相关例题:
POJ 2142
The Balance
大意:ax+by=c 求出x和y的绝对值,且|x|+|y| min,|ax|+|by| min
分析:利用ex_gcd()求得一组解x0,y0,得出通解的表达式:x=x0-Bt y=y0+At
|x|+|y|=|x0-Bt|+|y0+At|
将最开始的a,b设成a<b(不是就交换)——注意这很重要
这可以保证ex_gcd()得出的结果是x0<0,y0>0。即
小负大正
于是,我们有:
|x0-Bt|是一个凹函数(t<0 and t>=0)
|y0+At| 是一个增函数
且因为B=b/d A=a/d 所以较小的速度大于增大的速率。所以整体是一个凹函数。而那个凹点就是使得x0-Bt=0的t值。求出它并在其周围几个点枚举一下就能得到答案。(距离设为2能AC,但是设为1就不行了 T_T)
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
void ex_gcd(LL a,LL b,LL &d,LL &x,LL &y){
if(b==0){
d=a; x=1; y=0;
return ;
}
ex_gcd(b,a%b,d,x,y);
LL t=x;
x=y;
y=t-a/b*y;
}
LL abs(LL a){
return a<0?-a:a;
}
int main()
{
//freopen("cin.txt","r",stdin);
LL a,b,c;
while(~scanf("%lld%lld%lld",&a,&b,&c)){
if(a==0&&b==0&&c==0) break;
bool sp=0;
if(a>b){
sp=1;
a=a^b; b=a^b; a=a^b;
}
LL d,x0,y0;
ex_gcd(a,b,d,x0,y0);
x0=x0*(c/d);
y0=y0*(c/d);
LL t=x0*d/b,Min=0x3f3f3f3f,Min2=0x3f3f3f3f;
LL ans1,ans2;
for(int i=t-2;i<t+2;i++){
LL get=abs(x0-b/d*i)+abs(y0+a/d*i);
if(Min>get){
ans1=x0-b/d*i;
ans2=y0+a/d*i;
Min=get;
Min2=a*ans1+b*ans2;
}
else if(Min==get){
LL all=abs(a*(x0-b/d*i))+abs(b*(y0+a/d*i));
if(all<Min2){
Min2=all;
ans1=x0-b/d*i;
ans2=y0+a/d*i;
}
}
}
if(sp) { ans1=ans1^ans2; ans2=ans1^ans2; ans1=ans1^ans2; }
printf("%lld %lld\n",abs(ans1),abs(ans2));
}
return 0;
}
SGU 106 The equation
大意:求解方程ax+by+c=0在[x1,x2], [y1,y2]内解的个数。
相关知识:
拓展欧几里的求解不定方程:ax+by=c
假设x1<=x2, y1<=y2
假设x1<=x2, y1<=y2
当a=0,b=0时,如果c=0 解的个数是:(x2-x1+1)(y2-y1+1)
; 如果c!=0,解的个数是0
当
a=0
,
b!=0时,如果c%b=0,且-c/
b在y的要求范围内,那么解的个数是x2-x1+1
;
如果c%b!
=
0,或
-c/b不
在y的要求范围内,那么解的个数是0
当
a!
=0,b
=0时
过程类似。
当a!=0,b!=0,进入不定方程的解决程序:
方程转化成 ax+by=-c
拓展欧几里的a,b参数是正整数,所以将其进行相应的设置。
if(a<0) [x1,x2] --> [-x2,x1]
if(b<0) [y1,y2] --> [-y2,y1]
分开讨论:
x1, y1 确定下界
x2, y2 确定上界
图变成这个样子了:
所以,这样确定上下界:
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long LL;
void ex_gcd(LL a,LL b,LL &d,LL &x,LL &y){
if(b==0){
d=a; x=1; y=0;
return ;
}
ex_gcd(b,a%b,d,x,y);
LL temp=x;
x=y;
y=temp-a/b*y;
}
int main()
{
//freopen("cin.txt","r",stdin);
LL a,b,c;
LL x1,x2,y1,y2;
while(~scanf("%lld%lld%lld",&a,&b,&c)){
scanf("%lld%lld",&x1,&x2);
scanf("%lld%lld",&y1,&y2);
if(b==0){
if(a!=0){
if(c%a==0&&(-c/a)<=x2&&(-c/a)>=x1) { //one x but name y
printf("%lld\n",y2-y1+1);
}
else puts("0");
}
else {
LL ans=(x2-x1+1LL)*(y2-y1+1);
if(c==0)printf("%lld\n",ans);
else puts("0");
}
}
else if(a==0){
if(b!=0){
if(c%b==0&&(-c/b)<=y2&&(-c/b)>=y1) { //one y but many x
printf("%lld\n",x2-x1+1);
}
else puts("0");
}
else {
LL ans=(y2-y1+1LL)*(x2-x1+1);
if(c==0)printf("%lld\n",ans);
else puts("0");
}
}
else {
c=-c;
if(c<0){ c=-c; a=-a; b=-b; }
if(a<0) { a=-a; LL t=x1; x1=-x2; x2=-t; }
if(b<0) { b=-b; LL t=y1; y1=-y2; y2=-t; }
LL d,x,y;
ex_gcd(a,b,d,x,y);
if(c%d){
puts("0"); continue;
}
a/=d; b/=d; c/=d;
x=x*c; y=y*c;
LL l=max(ceil((y-y2)*1.0/a),ceil((x1-x)*1.0/b));
LL r=min(floor((y-y1)*1.0/a),floor((x2-x)*1.0/b));
if(r<l) puts("0");
else printf("%lld\n",r-l+1);
}
}
return 0;
}