• 【算法】欧几里得算法与青蛙约会oj


    欧几里得和扩展欧几里得算法

    题目:

    1. poj 1061
    2. poj 2142
    3. 双六

    扩展欧几里得算法详解

    先说欧几里得算法:欧几里得算法辗转相除求(gcd)。求(a、b)(gcd),则利用的性质是:(gcd(a,b)=gcd(b,a\%b)),而(gcd(a,0)=a),这样,辗转除下去,当第二个参数为0,第一个参数就是最大公约数。

    int gcd(int a,int b){
        while(b!=0){
            int tmp = a%b;
            a = b;
            b = tmp;
        }
        return a;
    }
    

    扩展欧几里得算法:扩展欧几里得算法不仅可以用来求最大公约数,还可以求逆元/满足ax+by=gcd(a,b)的x和y。基于的原理是:ax+by=gcd(a,b)一定存在解(x,y)。

    一个用处就是:问ax+by=1是否有解,就是看a,b是否互质,即gcd(a,b)=1。

    求满足ax+by=gcd(a,b)的(x,y)的过程,就是证明ax+by=gcd(a,b)一定有解的过程。

    (ax+by=gcd(a,b)=gcd(b,a\%b)=bx_2+a\%by_2)

    (a\%b=a-a/b*b)

    因此 (ax+by =bx_2+(a-a/b*b)y_2 =ay_2+b(x_2-a/b*y2))

    从而 (x = y2;y=x_2-a/by_2)

    层层递归下去,最终当(b=0)时,返回(gcd(a,b)=a),此时有一组解(x=1,y=0),回溯,利用上式求出(x,y)

    从而,这就建立了要求的(x,y)和前一状态的关系,算法的递归实现如下:

    int exgcd(int a,int b,int &x,int &y){
        /*输入ax+by=gcd(a,b)的a,b,返回gcd(a,b),同时求出满足此式的解(x,y)*/
        if(b==0){
            x = 1;
            y = 0;
            return a;
        }
        int p = exgcd(b,a%b,x,y);
        int tmp = x;
        x = y;
        y = tmp - a/b*y;
        return p;
    }
    int main(){
        int x,y;
        int gcd = exgcd(24,18,x,y);
        printf("24*%d + 18*%d = %d
    ",x,y,gcd);
        return 0;
    }
    

    继续.....

    仅仅知道欧几里得解(ax+by=gcd(a,b))还不够,扩展欧几里得最好用的地方在于其求解乘法逆元。所谓逆元:若(ax==1mod(m)),则称(x)(a)关于(m)的逆元,逆元可能有许多个,求的是其中最小的一个。

    (ax==1mod(m)) (=>) (ax+my=1)有解,求此式的解中最小的(x)即可。即求出此式(x)(x=x\%m),如果(x<0)(x+=m)

    求解代码借助exgcd:

    /*求逆元模板*/
    int exgcd(int a,int b,int &x,int &y){ 
        if(b == 0){ 
            x = 1; 
            y = 0; 
            return a; 
        } 
        int p =exgcd(b,a%b,x,y); 
        int temp = x; 
        x = y; 
        y = temp - (a/b) *y; 
        return p; 
    } 
    //求逆元 求a关于m的最小逆元 
    int cal(int a,int m){ 
        int x,y; 
        int gcd = exgcd(a,m,x,y); 
        if(1%gcd != 0) return -1; 
        x *= 1/gcd; 
        int ans = x % m; 
        if(ans<=0) ans+=m;  //保证正的且最小
        return ans; 
    }
    
    /*另一个简洁版本的求逆元*/
    void exgcd(int a,int b,int &x,int &y){
        if(b == 0){
            x = 1;
            y = 0;
        }else{
            exgcd(b,a%b,y,x);
            y = y - (a/b)*x; //此处应注意
        }
    }
    

    例题:青蛙约会poj 1061

    代码解释:https://blog.csdn.net/liangdong2014/article/details/38732745

    poj 1061 青蛙约会
    两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。  我们把这两只青蛙分别叫做青蛙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
    

    思路:

    在一个周长是(L)的环上,A青蛙处于(x)处,每次跳(m),B青蛙处于(y)处,每次跳(n),跳相同的步数后,两青蛙处于同一位置。由此,设跳(t)步相遇,则:

    ((x+m*t)\%L=(y+n*t)\%L)

    (=>(m-n)*t\%L=(y-x)\%L)

    (=>(m-n)*t\%L=y-x)

    若此式有解,则应有,

    (=>(m-n)*t_1+L*t_2=y-x)有解,由exgcd知,有解的条件是

    (=>(y-x) \% gcd(m-n,L) =0)

    若有解,下面求解满足条件的最小(t_1),进而推出(t)

    由exgcd求出满足((m-n)*t_1+L*t_2=gcd(m-n,L))的一组解:

    exgcd(m-n,L,t1,t2);

    由于(y-x)(gcd(m-n,L))的整数倍,则((m-n)*t_1+L*t_2=y-x)的一个解是:

    (=>t_1=t_1*(y-x)/gcd(m-n,L));

    注意这样求出来的(t_1)可能并不是最小,因此对(t_1)做如下操作:

    (=>t_1=t_1\%L)变为最小;

    (t_1)还可能是负值,因此,变为正的:

    (=>if_{ t_1<0}:t_1=t_1+L;)

    从而解出了(t=t_1)

    实现代码:

    #include<iostream>
    using namespace std;
    typedef long long ll; 
    ll x,y,m,n,L;
    ll gcd(ll a,ll b){
    	while(b!=0){
    		ll tmp = a%b;
    		a = b;
    		b = tmp;
    	}
    	return a;
    }
    void exgcd(ll a,ll b,ll&x,ll &y){
    	if(b == 0){
    		x = 1;
    		y = 0;
    	}else{
    		exgcd(b,a%b,y,x);
    		y = y - (a/b)*x;
    	}
    }
    
    int main(){
    	ll a,b,c,d,t1,t2,L2;
    	while(scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L)!=EOF){
    		a = m-n;
    		b = y-x;
    		d = gcd(a,L);
    		if(b % d != 0){
    			printf("Impossible
    ");
    		}else{
    			exgcd(a,L,t1,t2);
    			t1 = t1*b/d; //先变换到求at1+Lt2=y-x,再求最小,不能先求最小再变换,否则题意不符 
    			t1 = t1 % L;
    			if(t1<0){
    				t1 += L;
    			}
    			printf("%lld
    ",t1);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    线程池全面总结
    有状态(Stateful)与无状态(Stateless)
    Callable接口--有返回值的线程
    集合类--最详细的面试宝典--看这篇就够用了(java 1.8)
    [Android App]IFCTT,即:If Copy Then That,一个基于IFTTT的"This"实现
    应朋友死皮白咧地邀请贴一个招聘广告
    [Android]Android焦点流程代码分析
    [Android]Android内存泄漏你所要知道的一切(翻译)
    [Android]Gradle 插件 DiscardFilePlugin(class注入&清空类和方法)
    [Android]使用RecyclerView替代ListView(四:SeizeRecyclerView)
  • 原文地址:https://www.cnblogs.com/duye/p/10487665.html
Copyright © 2020-2023  润新知