exgcd(扩展欧几里得),欧几里得

目录

欧几里得定理及证明及代码

扩展欧几里得及证明及代码

裴属定理及证明

扩欧x,y的解的转换及求取最小整数解的证明
T1青蛙的约会

   负数的模运算,代码修改过程

T2倒酒

T3荒岛野人Savage

(我写博客基本都是对自己不太熟练的地方做一个梳理,所以大概不太会考虑读者的阅读体验)

欧几里得:

用来求最大公约数的一个公式

公式:gcd(a,b)=gcd(b,a%b)=d;

证明:设 a=md,b=nd,显然,m与n互质(它俩要不互质那最大公约数就不是d,我们已经规定最大公约数为d)

          再设 a=qb+r  md=qb+r 

          那么 r=a-qb=md-ndq=d(m-qn)

                 gcd(b,r)=gcd(nd,d(m-qn))

                 因为m与n互质,所以n与m-qn互质

                m和n本身就是互质的,你无论在m里怎么减去一个n的倍数,该互质还是互质,如果减去一个n的倍数就不互质了,即m-qn=kn,那m=n(q+k),说明原本就不互质

                所以gcd(b,r)=d;r=a%b 得证

代码

查看代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int mx=30;
int gcd(int a,int b){
	if(b==0)return a;
	return gcd(b,a%b);
}
void Solve(){
	int a,b;
	scanf("%d%d",&a,&b);
	int d=gcd(a,b);
	printf("%d\n",d);
}
int main(){
	Solve();
	return 0;
}

扩展欧几里得:

在求a,b的最大公约数的同时顺带求了 ax+by=gcd(a,b)的以一组解

证明:这个其实也简单

        ax1+by1=gcd(a,b);

        bx2+a%by2=gcd(b,a%b);

        因为gcd(a,b)=gcd(b,a%b)

        所以 ax1+by1=bx2+a%by2

        因为 a%b=a-a/b*b (显然a/b是指int下的向下取整的整数解)

        所以 ax1+by1=bx2+(a-a/b*b)y2=bx2+ay2-a/b*by2=ay2+b(x2-a/b*2)

                ax1+by1=ay2+b(x2-a/b*y2)

        那么我们就可以令 x1=y2,y1=x2-a/b*y2;得出答案

代码

查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int mx=30;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;
		y=0;
		return a;
		//先求出 a*x+y*0=gcd(a,b)=a 的一组解 
	}
	int ret=exgcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-a/b*y;
	//再根据公式回溯时把解代换出来 
	return ret;
}
void Solve(){
	int a,b;
	scanf("%d%d",&a,&b);
	int x,y;
	int d=exgcd(a,b,x,y);
	printf("d=%d x=%d y=%d\n",d,x,y);
}
int main(){
	Solve();
	return 0;
}

做题心得及体会:

俗话说的好(我说的):知识掌握的再透有什么用?你得会做题啊

其实如果真去做题就会发现有很多技巧和推论真不是好搞的

如果是实际题目 基本最后都可以归纳成求解ax+by=m

说到这里让我们介绍一下裴蜀定理

  • 裴蜀(贝祖)定理

定理:若ax+by=m有解,那么m一定是gcd(a,b)的若干倍

证明:我的证明可能略显奇怪(以后没有特殊说明默认d=gcd(a,b))

         设ax+by=d ,

         c*ax+c*by=d*c 显然

         d*c=m 得证

         这好像根本就没有证明啊

         不过这确实是做题最常用的推论

         换个思路

        直接设ax+by=m

          d一定可以被a,b整除

          d一定可以被ax,by整除

          d一定可以被ax+by整除

          ax+by就是m 得证

大概就是这样,我们求出的ax+by=gcd(a,b)就可以扩展到任意解了

顺带把那俩也证一下

x,y的解的转换及求取最小整数解:

这个真的做题必用,看不懂证明也要把代码背过

         再设ax1+by1=d

         ax+by-ax1-by1=0

         a(x-x1)+b(y-y1)=0

         两边同除d

         a(x-x1)/d=-b(y-y1)/d

        因为gcd(a/d,b/d)=1; d本身就是它们的gcd,除去d它们的公因数就只剩1了

        所以 (x-x1)是 b/d的整倍数

       这个因为其实并不怎么所以。详细说一下:首先,a/d*(x-x1)是b/d的整倍数

       设 a,b互质 k=ma=nb   就是说,k除去b后,依旧是a的整倍数,k除去的因数中没有一个与a相关 勉强算解释了一下

       继续 : x-x1=b/d*t   x=x1+b/d*t 由此可以由一组解推出无穷多的解

                   y这里同理 只不过注意一正一负

                    y=y1-b/d*t      这是显然,上面原式本身就有一个负,总得给一个,且加和不变,一个大了另一个总得小

                   这里强调第一次,在做所有的扩欧题,无论是使用推论还是怎样,永远不要忘了你求解的是原式,原式

       那么这个同样可以看出 所有x之间有一个加b/d*t的线性关系

       x(任意且为正)=xmin+b/d*t  把所有的b/d*t消去就是xmin

       所以要求最小正整数(最小负没有)解:xmin=x%(b/d) 是不是很妙 

下面说的这几道题洛谷上都有

青蛙的约会  

这个题显然,就是求 (x+km)%L=(y+kn)%L

求出这个k,把上面的式子到换一下 先换成同余式,再根据同余恒等变换

 (x+km) = (y+kn) (mod L)

 (x+km)/L=t......(y+kn)  (这里的(x+kn)和(y+kn) 不一定互质)

  (x+km)=L*t.....(y+kn)

   (x+km)-(y+kn)=L*t

其实在用exgcd去求逆元的时候也是这么一套流程,甚至求逆元比这个还简单一些

在做扩欧的时候,关键是找出ax+by=m的形式

所以转换上式  k(m-n)-l*t=y-x 设a=m-n,b=t

                       ka-bt=y-x 

                      那么这里注意,问题来了,且问题很复杂

我们可以直接将其转化为 ka+bt=y-x

                                     只需要最后记得将t取负即可

但是,如果 a<0呢 

这里提一下我犯过的nt错误: ax-by=-ax+by 把b的负号给到a上,这是错的错的!

再次强调,我们要求解的原式永远是:  ka-bt=y-x 

根据我的实践,-7和3 3和-7 4和-16 -16和4 的d  在c++下d的绝对值是正数的d,但是d往往一正一负我也不知道为啥 且-7和3是-1,-16和4是4

但是,但是,它们求解出的x,y都是对的

总之就是 你去求解原式,不管d有多么离谱,答案就是那个答案,一定不会错,但是本题要求x1肯定得是最小正整数

所以问题来了,a可能为负,y-x也可能为负,怎么搞使得我的答案是最小正整数

首先引理,c*x1=c*x+b/d 它们之间也存在这个关系,这是很显然的

所以可以先直接求解出来(不进行任何换号操作,当然默认换y的号,y如果要求输出记得换号)

再乘上(y-x)/d ,再写个while把x的值正过来即可

这里补一句,自己回看差点没看懂

求解出来的 x1是 ax1+by2=gcd(a,b)的一个解

但实际上他应该等于 y-x ,所以这里我们要把x1乘上一个(y-x)/d

代码

查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int mx=1000;
ll x,y,m,n,l;
ll exgcd(ll a,ll b,ll &x1,ll &y1){
	if(b==0){
		x1=1;
		y1=0;
		return a; 
	} 
	ll ret=exgcd(b,a%b,x1,y1);
	ll t=x1;
	x1=y1;
	y1=t-a/b*y1;
	return ret;
}
void Solve(){
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
	ll a=y-x,b=m-n;
	ll x1,y1;
	ll an=exgcd(b,l,x1,y1); 
	if(a%an!=0){
		printf("Impossible\n"); 
		return ;
	}
	x1=x1*(a/an);
	x1=x1%(l/an);
	while(x1<=0){
		if(l/an<0)x1=x1-l/an;
		else x1=x1+l/an;
	}
	printf("%lld\n",x1);
}
int main(){
	Solve();
	return 0;
}

然后还有两个问题,明天再说,干饭去了

其实也就是一个了,首先上面那个while可以转换成一行

x1=((x1*(a/an))%(l/an)+(l/an))%(l/an);

不过这要保证an为正

为什么可以这么转换,就是关于mod运算在负数方面的操作了,再把这个问题解决,基本上放眼一看,扩欧全是一种套路模型(如果没有跟别的东西结合)(仅代表个人观点)

负数的mod运算

负数mod在不同语言下的答案往往很不一样

这里仅仅介绍在c++下的运算(因为我用c++ ----大雾),(c++跟系统计算器的有一小些地方还不一样,当然这跟不同计算器的不同版本甚至类型也有关,所以做题时关于负数mod一定要写代码,不要直接相信计算器)

说取模之前得先说除法

除法:

向零取整(趋零截尾)(truncate toward zero):向0方向取最接近精确值的整数,换言之就是舍去小数部分,因此又称截断取整。在这种取整方式下

7/4=1        7/(-4)=-1          6/3=2         6/(-3)=-2    -7/-4=1 (这里都是int类型)

c++都是采用truncate除法,这样可以使得,(-a)/b==-(a/b) 一定成立

mod法:(魔法??临时想的名字感觉很帅)

设a=qb+r 

一般都会满足 r=a-(a/b)*b

对C++而言,该式非常重要,这是解决负数取模问题的关键
7%(-4)
7/(-4)=-1  余数=7-(-4)*(-1)=3   7%(-4)=3
(-7)%4
(-7)/4=-1  余数=(-7)-4*(-1)=-3   (-7)%4=-3
(-7)%(-4)
(-7)/(-4)=1  余数=(-7)-(-4)*1=-3  (-7)%(-4)=-3

所以我们可以显然得出 即使在负数mod中,余数的绝对值要小于模数的绝对值

所以如果保证了an为正

直接x1=((x1*(a/an))%(l/an)+(l/an))%(l/an);即可

代码

查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int mx=1000;
ll x,y,m,n,l;
ll exgcd(ll a,ll b,ll &x1,ll &y1){
	if(b==0){
		x1=1;
		y1=0;
		return a; 
	} 
	ll ret=exgcd(b,a%b,x1,y1);
	ll t=x1;
	x1=y1;
	y1=t-a/b*y1;
	return ret;
}
void Solve(){
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
	ll a=y-x,b=m-n;
	if(b<0){
		b=-b;
		a=-a; //a跟b配套,b换a也换 
		//以此来保证模数为正 
	} 
	ll x1,y1;
	ll an=exgcd(b,l,x1,y1); 
	if(a%an!=0){
		printf("Impossible\n"); 
		return ;
	}
	x1=((x1*(a/an))%(l/an)+(l/an))%(l/an);
	printf("%lld\n",x1);
}
int main(){
	Solve();
	return 0;
}

当然我不太喜欢这种写法,去倒正负号好麻烦的,其实如果在while上面取mod,也只是一个if判断罢了,while实际上也就执行了一次

如果把mod写在while下面,while确实是发挥了很大作用,跑了好多次,时间直接翻了十倍(狗头)

所以我最喜欢

查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int mx=1000;
ll x,y,m,n,l;
ll exgcd(ll a,ll b,ll &x1,ll &y1){
	if(b==0){
		x1=1;
		y1=0;
		return a; 
	} 
	ll ret=exgcd(b,a%b,x1,y1);
	ll t=x1;
	x1=y1;
	y1=t-a/b*y1;
	return ret;
}
void Solve(){
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
	ll a=y-x,b=m-n;
	ll x1,y1;
	ll an=exgcd(b,l,x1,y1); 
	if(a%an!=0){
		printf("Impossible\n"); 
		return ;
	}
	x1=x1*(a/an);
	x1=x1%(l/an);
	if(x1<=0){
		if(l/an<0)x1=x1-l/an;
		else x1=x1+l/an;
	} 
	printf("%lld\n",x1);
}
int main(){
	Solve();
	return 0;
}

 我们在这个题把一般模型的代码细节的前因后果都讲透了,后面基本就是找出扩欧式然后套代码

倒酒

题意的一个小理解:b酒杯中的酒可以剩下(把a倒满了),下次再给a倒进去

也就是求 ans=by-ax  的一组最小整数解

ans,x,y都为所求 

ans,x,y都要输出的话就需要注意x的换号了,一些关于本题的细节都放在代码里了

代码

查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	int ret=exgcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-a/b*y;
	return ret;
}
void Solve(){
	int a,b;
	scanf("%d%d",&a,&b);
	// ans=-a*x+b*y 把a的符号给到未知数x上 
	int x,y;
	int d=exgcd(a,b,x,y);
	x=-x;a=-a;
	//把x换号,x换回来,a得换回去 
	//我们根据题意已知a,b,d都为正
	x=x%(b/d);
	y=y%(a/d);
	if(x<0 || y<0){
		x=x+b/d;
        y=y-a/d;
        //这里显然两个都是会越来越大
		//因为-a本身都是负的,所以同大 
	}
	if(y==0)y=1;//至少得倒一杯
	//题库里把 x=x%(b/d);y=y%(a/d),删了不用加这个if
	//可以直接A,但是显然,逻辑上说不过去,且会被hack 
	printf("%d\n%d %d\n",d,x,y); 
}
int main(){
	Solve();
	return 0;
}

荒岛野人Savage

求得一个M,使得任意两个野人之间的

c1 + x*p1 = c2+ x*p2 (mod M) 同余方程都无解

 c1-c2 + x*(p1-p2)=m*k
 x*(p1-p2) - m*k =c2-c1

不要管同余方程无解是什么意思,关于exgcd,c2-c1除以d不是整数就无解

可以枚举M的值,每一个M,都循环所有的野人判断,n<=15显然是可行的

x的值的倒换还是用的我喜欢的方法,再说句不要脸的,我自创的麻烦法

查看代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int mx=30;
struct Node{
	int c;
	int p;
	int l;
}r[mx];
int n;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	int ret=exgcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-a/b*y;
	return ret;
}
bool check(int m){
	int x,y;
	for(int i=1;i<=n;++i){
		for(int j=i+1;j<=n;++j){
			int a=r[i].p-r[j].p;
			int b=m;
			int s=r[j].c-r[i].c;
			int d=exgcd(a,b,x,y);
			if(s%d) continue;//不能整除是无解可以直接跳了 
		    x=x*s/d;
		    x=x%(b/d);
		    if(x<=0){
		    	if(d<0)x=x-b/d;
				else x=x+b/d; 
			}
		    if(x<=r[i].l && x<=r[j].l)return 0;//只要有一对野人会相遇,这个M直接pass 
		}
	}
	return 1; 
}
void Solve(){
	scanf("%d",&n);
	int M=0; 
	for(int i=1;i<=n;++i){
		scanf("%d%d%d",&r[i].c,&r[i].p,&r[i].l);
		M=max(r[i].c,M);
	}
	//求得一个M使得同余方程无解,
	// c1 + x*p1 = c2+ x*p2 (mod m)
	// c1-c2 + x*(p1-p2)=m*k
	// x*(p1-p2) - m*k =c2-c1
	while(1){
		int an=check(M);
		if(an==1){
			printf("%d\n",M);
			return ;
		}
		M++;
	}	
}
int main(){
	Solve();
	return 0;
}

 

posted @ 2022-05-17 12:09  SMTwy  阅读(231)  评论(0编辑  收藏  举报