辗转相除法及扩展欧几里得算法
1辗转相除法
1.1
辗转相除法多用于求最大公约数,其算法的核心在于做有限次带余除法,最大公约数的值不变,即\(gcd(a,b)=gcd(b,a-a/b*b)\)(即a%b)
证明: 如果z是a和b的公因子,即z整除a,z整除b,则z必然整除a-b,所以z是a-b的因子,所以z是a,b,a-b的公因子
如果z不是a的因子,那么即使z是b的因子,z也不是a和b的公因子,z不是b的因子更不用提
如果z不是b的因子,同上可得z不是a和b的公因子。
所以a和b的公因子和a和a-b,b和a-b的公因子一样,则最大公因数也一样,所以\(gcd(a,b)=gcd(b,a-b)\),所以\(gcd(a,b)=gcd(b,a-a/b*b)\)
a能整除b时,b即为最大公约数,这时a%b等于0
1.2算法实现
inline int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
2扩展欧几里得算法
2.1
根据贝祖定理,ax+by=c的充分必要条件是c整除gcd(a,b)
证明:
可以得到\(ax+by=gcd(a,b)=gcd(b,a-a/b*b)=b*x_0+(a-a/b*b)*y_0=a*y_0+b*(x_0-a/b*y_0)\)
根据上面这个式子,可以得到:根据辗转相除,a-a/b*b最终会变成0,而这时x_0等于1,y_0等于0明显符合上式,原式答案可以经过下面的递归式算出,同样也是根据上面这个式子:
\(x=y_0\)
\(y=x_0-a/b*y_0\)
同时,这说明一定存在整数x,y,满足方程ax+by=gcd(a,b),故有c必须被gcd(a,b)整除。
再取得特解后,发现\(x=x_0-b/gcd(a,b)*t,y=y_0+a/gcd(a,b)*t\)中x,y也满足原方程,所以我们得到了上面方程的通解
2.2算法实现
inline ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;y=0;
return a;
}
ll gcd=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return gcd;
}
2.3辗转相除法的二进制算法
其算法核心在于除去因子2以降低常数,提高速度
核心:
- 若a、b都为偶数,那么\(gcd(a,b)=2*gcd(a/2,b/2)\)
- 若a为偶数,b为奇数,\(gcd(a,b)=gcd(a/2,b)\)
- 若a为奇数,b为偶数,\(gcd(a,b)=gcd(a,b/2)\)
- 若a、b都为奇数,\(gcd(a,b)=gcd(b,a-b)\)
2.4代码
inline int gcd(int a,int b){
int i,j;
if(a==0) return b;
if(b==0) return a;
for(i=1;(a&1)==0;i++) a>>=1;
for(j=1;(b&1)==0;j++) b>>=1;
if(j<i) i=j;
while(1){
if(a<b) a^=b,b^=a,a^=b;//swap(a,b)
if(0==(a-=b)) return b<<i;
while(a&1==0) a>>=1;
}
}
3 题目P1516青蛙的约会
https://www.luogu.com.cn/problem/P1516
提示:把题目代入数学模型,发现是一个不定方程,由此由扩展欧几里得算法得到一组特解,再求最小正整数解时,对它进行取模运算,同时不要忘了负数的处理。
由于总是快的去追慢的,所以要注意n-m的正负。
3.1代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 100010
#define M number
using namespace std;
ll x,y,m,n,l;
inline int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
inline ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;y=0;
return a;
}
ll gcd=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return gcd;
}
int main(){
scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
ll t,p,a=n-m,b=x-y;
ll gcd=exgcd(l,a,p,t);
if(b%gcd!=0){
printf("Impossible\n");
return 0;
}
printf("%lld",(b/gcd*t%(l/gcd)+l/gcd)%(l/gcd));
return 0;
}