欧几里得算法
欧几里德算法又称辗转相除法,用于计算两个正整数a,b的最大公约数(gcd)。
其计算原理依赖于下面的定理:
定理:gcd(a,b) = gcd(b,a mod b) (a>b 且a mod b 不为0)
证明:a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有
d|a,d|b,而r = a - kb,因此d|r
因此d也是(b,a mod b)的公约数
因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证
或:证明:
第一步:令c=gcd(a,b),则设a=mc,b=nc
第二步:根据前提可知r =a-kb=mc-knc=(m-kn)c
第三步:根据第二步结果可知c也是r的因数
第四步:可以断定m-kn与n互素【否则,可设m-kn=xd,n=yd,(d>1),则m=kn+xd=kyd+xd=(ky+x)d,则a=mc=(ky+x)dc,b=nc=ycd,故a与b最大公约数≥cd,而非c,与前面结论矛盾】
从而可知gcd(b,r)=c,继而gcd(a,b)=gcd(b,r),得证。
欧几里得(GCD)de递归写法:
int gcd(int a,int b)
{
if(b == 0)
return a;
else
return gcd(b,a%b);
}
//更简单写法
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;//(或return b==0?a:gcd(b,a%b);)
}
由最大公约数又可得最小公倍数(LCM)
lcm(a,b) = (a*b)/gcd(a,b); 代码如下:
int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
//为了防止a*b超long long 溢出,所以建议下面这种写法
long long(long long a,long long b)
{
return a/gcd(a,b)*b;
}
gcd lcm 两个公式的推广:
gcd(k*a,k*b) = k*gcd(a,b);
lcm(k*a,k*b) = k*lcm(a,b);
//这两个公式利用gcd(a/c,b/c) = 1 (注:c = gcd(a,b))可以推理证明
扩展欧几里得算法
内容:若gcd(a,b) = d,那么一定存在x,y使得 ax+by = d,这是一个不定方程,一定有多组解,但是只要找到一组特解x0,y0,就能得到不定方程的通解:
x=x0+b/gcd*k;
y=y0+a/gcd*k;(k为整数)
扩展欧几里得算法就是在求a,b的最大公约数的同时顺带着把ax+by=d的通解求出来的过程,代码与欧几里得差不多,也是采用的递归写法 : tx=y; ty=x-(a/b)*y(两个相邻状态之间的关系,证明过程不会= =);
//扩展欧几里得
ll exgcd(ll a,ll b,ll &x,ll &y){
//注意x和y必须是引用
if(!b){x=1,y=0;return a;}
int d=exgcd(b,a%b,x,y);
int t=x;x=y;y=t-(a/b)*y;
return d;
}
扩展欧几里得可以判断不定方程ax+by=c是否有整数解,好像欧几里得也是可以判断的-_-||,d=gcd(a,b);如果c可以整除d,那么不定方程ax+by=c有整数解,否则没有。
ax+by=c;
d=gcd(a,b);
if(d%c==0)//有整数解
{
x=c/d*x0+b/d*k;
y=c/d*y0-a/d*k;//k为整数
}
else
{
无整数解
}
因为 ax0+by0=gcd
所以 a(x0+b/gcd*t)+b(y0-a/gcd*t)=gcd
所以 x=x0+b/gcd*t, y=y0-a/gcd*t (t为循环变量)
扩展欧几里得还能求逆元(*^▽^*)
使用条件: a,b为正整数,而且gcd(a,b) = 1
证明: 因为a,b 互质,所以一定有 ax+by = 1
两边同时对b 取余
ax%b + by %b = 1%b -------> ax%b = 1%b
即 ax ≡ 1 (mod b)
扩展欧几里得中x 就是a关于b的逆元
同理y 就是 b 关于 a的逆元
所以使用完欧几里得算法,我们判断返回值d 是否为1,为1说明 gcd(a,b) = 1
即求得的x就是 a关于b的逆元
void inv(ll a,ll b)
{
ll x,y;
if(exgcd(a,b,x,y) == 1)
cout<<"inv(a):"<<x<<endl;
else
cout<<"不存在逆元"<<endl;
}
UPC 9511 Utawarerumono欧几里得判断不定方程是否有整数解,本来以为需要扩展欧几里得,后来发现欧几里得加暴力就可以,不过要判断a,b,c取值的多种情况
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//ll exgcd(ll a,ll b,ll &x,ll &y){
// //注意x和y必须是引用
// if(!b){x=1,y=0;return a;}
// int d=exgcd(b,a%b,x,y);
// int t=x;x=y,y=t-(a/b)*y;
// return d;//d是a和b的gcd,顺便求出来
//}
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
int main()
{
ll t,ans;
ll a,b,c,p1,p2,q1,q2;
scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&p1,&p2,&q1,&q2);
// ll x,y;
ll d=gcd(a,b);
if(a==0&&b==0&&c==0)
{
printf("0\n");
return 0;
}
if(a==0&&b==0&&c!=0)
{
printf("Kuon\n");
return 0;
}
if(a!=0&&b==0)
{
if(c%a!=0)
{
printf("Kuon\n");
}
else
{
t=c/a;
ans=p2*t*t+p1*t;
printf("%lld\n",ans);
}
return 0;
}
if(a==0&&b!=0)
{
if(c%b!=0)
{
printf("Kuon\n");
}
else
{
t=c/b;
ans=q2*t*t+q1*t;
printf("%lld\n",ans);
}
return 0;
}
if(c%d==0)
{
ll minn=1e18;
for(ll i=-1e6;i<=1e6;i++)
{
if((c-a*i)%b==0)
{
ll t=(c-a*i)/b;
ll ans=p2*i*i+p1*i+q2*t*t+q1*t;
minn=min(minn,ans);
}
}
printf("%lld\n",minn);
}
else
printf("Kuon\n");
return 0;
}
HDU 2669 扩展欧几里得裸题
这道题只需要注意两点:gcd(a,b)=1(这个好办,求gcd就行了);还有就是x>=0,这就需要用到扩展欧几里得的通解公式了,如果x<0,可以把x一直加b/gcd(a,b),y一直减a/gcd(a,b),直到x>=0为止。
AC代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
ll d=exgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-(a/b)*y;
return d;
}
int main()
{
ll a,b,x,y;
while(cin>>a>>b)
{
ll d=exgcd(a,b,x,y);
if(d==1)
{
while(x<0)
{
x+=b;
y-=a;
}
cout<<x<<" "<<y<<endl;
}
else
cout<<"sorry"<<endl;
}
return 0;
}