gcd与扩展gcd-poj1061青蛙的约会

目录

1.什么是gcd算法,什么又是扩展gcd?

2.gcd和扩展gcd有什么用

(1).gcd

(2).扩展gcd

3.例题:

1.poj1061 青蛙的约会


1.什么是gcd算法,什么又是扩展gcd?

    首先gcd算法是一个很古老的用于计算两数最大公约数的算法,而扩展gcd是基于gcd的一个扩展算法,用于求解模线性方程ax≡b (mod n).

2.gcd和扩展gcd有什么用

(1).gcd

    gcd用于求两数最大公约数是用到了一个定理

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

    这个定理的证明可参阅算法导论(第二版) P526 ,这里就不做证明了.

    由于这个定理的成立,b是不断减小的终会趋向于0,故而有如下算法:

int gcd(int a.int b){ //返回a,b的最大公因数
	if(b==0) return a;
	return gcd(b,a%b);
}

    当然也可使用递归实现:

int gcd(int a,int b){ //返回a,b的最大公因数
	while(b){
		int t=a;
		a=b;
		b=a%b;
	}
	return a;
}

    这里顺便提一个小细节,a与b的最大公倍数可以通过a*b/gcd(a,b)得到.

(2).扩展gcd

    然后是扩展gcd,当我们已知正整数a,b,c,想要求得满足ax+by=c的一组整数x,y时,就可以使用扩展gcd,因为当我们试图求ax+by=c时,若c不是gcd(a,b)的倍数,答案是肯定不存在的,因为毕竟在等式成立的情况下c是有一定数目的ab加起来得到的,自然ab的最大公因数也应该是c的因数.

    故而我们可以通过求得满足ax+by=gcd(a,b)的解tx,ty然后满足ax+by=c的解答自然就是

                x=tx*(c/gcd(a,b)),y=ty*(c/gcd(a,b)); 

    现在我们需要想办法求出满足ax+by=gcd(a,b)的x,y为多少?

        我们会发现当普通的gcd循环到底层的时候gcd(a,b)=a,此时x,y的值固定为1,0;

    故而我们可以在gcd返回的同时返回x,y,并且调整x,y为当前层正确的值就好,最终返回到顶层的x,y就是我们要的答案.

    最大的难点其实在于下层返回给上层的xy是建立与下层的ab得到的答案,我们怎么以之得到上层的a,b下的x,y,如下

假设在第二层b2为0了,递归开始向上返回

 第一层       a1x1+b1y1=gcd(a1,b1)    

 第二层       a2x2+b2y2=gcd(a2,b2)    

因gcd过程中gcd(a1,b1)=gcd(a2,b2),

    故而 a1*x1+b1*y1=a2*x2+b2*y2

    又因 a2=b1, b2=a1%b1=a1-a1/b1*b1

    故而 a1*x1+b1*y1=b1*x2+(a1-a1/b1*b1)*y2 -> a1*x1+b1*y1=a1*y2+b1*(x2-a1/b1*y2)

    故而 x1=y2,y1=x2-a1/b1*y2 时第一层的等式也成立,由此可以以第二层返回的x2,y2推出上一层的x1,y1

思路就是这样子

    扩展gcd模版代码如下:

ll extgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	ll d=extgcd(b,a%b,x,y);
	ll t=x;
	x=y;
	y=t-a/b*y;
	return d;
}

针对与求解ax+by=c,还得在扩展gcd后处理一下x避免不存在解或解x为负数

ll cal(ll a,ll b,ll c){ //计算ax+by=c的满足条件的x 
    ll x,y;
    ll gcd=extgcd(a,b,x,y);
    if(c%gcd!=0) return -1; //不存在解 
    x*=c/gcd;
    b/=gcd;
    if(b<0) b=-b;
    ll ans=x%b;
    if(ans<=0) ans+=b; //对ans为负的特殊处理+ 
    return ans;
}

3.例题:

1.poj1061 青蛙的约会

                                                                                        青蛙的约会

Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 126336   Accepted: 27209

Description

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。 
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。 

Input

输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。

Output

输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"

Sample Input

1 2 3 4 5

Sample Output

4

 

题意分析:

1.题是什么?

    世界被看做一条长度为L的首尾相接的线,两只青蛙分别在x,y位置,他们一起向西跳,每步分别跳m,n,跳是同步的,问在多少次跳跃后相遇,不能就impossible.

2.思路

    青蛙相距距离知道了为x-y,mod也知道了是赤道长度L,因为两个青蛙跳的速度一样故而可以认为每个时间步移动m-n,故而其实就是求一个线性同余方程 (m-n)*ans=(x-y) mod L,看是否存在ans满足答案.

3.小细节

    求完答案之后为了避免ans是负数一定要记得正常情况下要调用cal函数后面那部分去再处理扩展gcd的结果.

ac代码

#include <stdio.h>
typedef long long ll;

ll extgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	ll d=extgcd(b,a%b,x,y);
	ll t=x;
	x=y;
	y=t-a/b*y;
	return d;
}


ll cal(ll a,ll b,ll c){ //计算ax+by=c的满足条件的x 
    ll x,y;
    ll gcd=extgcd(a,b,x,y);
    if(c%gcd!=0) return -1; //不存在解 
    x*=c/gcd;
    b/=gcd;
    if(b<0) b=-b;
    ll ans=x%b;
    if(ans<=0) ans+=b; //对ans为负的特殊处理+ 
    return ans;
}

void solve(){
	//两只青蛙能跳到一起 -> (m-n)ans=ty-tx+lk -> (m-n)ans+lk=ty-tx ans是答案,k是整数
	ll tx,ty,m,n,l;
	while(scanf("%lld%lld%lld%lld%lld",&tx,&ty,&m,&n,&l)!=EOF){
		ll res=cal(m-n,l,ty-tx); //传入abc
		if(res==-1){
			printf("Impossible\n");
			continue;
		}
		else printf("%lld\n",res);
	}
}

int main(){
	solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_31964727/article/details/81020928