码迷,mamicode.com
首页 > 其他好文 > 详细

拓展欧几里得详解 及其题目 POJ 1061 2115 2142 UVA 10673 10090

时间:2016-08-22 15:01:30      阅读:301      评论:0      收藏:0      [点我收藏+]

标签:

               最近做了一些拓展欧几里得的题目呢,嘛,从一开始的不会到现在有点感觉,总之把我的经验拿出来和大家分享一下吧。

               普通的欧几里得是用于解决求两个数a,b的gcd的,但是我们知道,gcd是线性组合 { ax+by | x,y∈Z }里的最小正元素(什么?不知道怎么来的?好吧。。。算法导论里数论算法那一章有证明),假若我们能够把这个x和y找出来,那么可以用来解决很多问题。

               (以下的gcd和lcm均指(gcd(a,b)和lcm(a,b))

               首先,假设ax+by=gcd这一个方程有一个特解x*,y*。那么显然,我们可以构造出这个方程的通解x0=x*+k*lcm/a,y0=y*-k*lcm/b,k∈Z。拓展欧几里得就能够帮我们把x*和y*求出来,这是用途之一。

               其次,假若我们面对的方程是ax+by=d的形式,解是整数的情况下,我们可以利用上面说的解来推导出这个方程的解。

                          ①假若gcd 不能整除 d,那么此时这个方程无整数解

                              证明: 假设存在整数解 n ,m 使得 an+bm=d,那么此时左右两边同时除以gcd有

                                          a/gcd*n+b/gcd*m=d/gcd        显然,左边的和依然是整数,右边是分数,因为gcd不能整除d ,矛盾

                                          所以此方程无整数解,证毕。

                          ②在gcd是d的一个因子的情况下,这个方程可以变成   a*(x*gcd/d) + b*(y*gcd/d) = gcd,于是就转化成为了第一个问题的求解。但此时在求解通解的时候需要注意,该方程的通解并不是原来的通解乘上一个d/gcd这么简单。我想有一些人会这么做

                                          x*gcd/d=x0,y*gcd/d=y0,因此,此时的通解就是 x = d*x0/gcd, y= y0*d/gcd 

                           假若仅仅只是原来的通解乘上一个d/gcd,那么这只是现在方程的通解的一个子集而已,并不是通解。举个例子,假设d/gcd=2,那么你原来的k∈Z,现在乘上一个d/gcd之后,k的范围就变成了偶数,丢失了一部分的解。正确的是仅仅只使用原来的特解,也就是下面这一种情况。

                                        x = d*(x*)/gcd + k*lcm/a , y = d*(y*)/gcd + k*lcm/b

                           只有这样的情况才是这个方程的通解,我做的很多题目都是需要对k的范围加以限定,然后选出合适的解

                 再者,可以用来求解一次同余式的问题,相信这个转化不是很难 。假设我们遇见的方程是这样的 ax≡ d(mod b),我们可以轻易的变成 ax + by = d的形式,也就是上面讨论过的那一个形式,不过在这里我们只需要一个x即可。


                 接下来讲解例题

                                  POJ 1061  青蛙的约会 

                                                   这一道题目就是上面说的第三种情况,求解一次同余式的问题,这道题目的一个式子就是   x + k*n ≡ y + k *m (mod L )通过移项后可以变成 k*(n-m)≡ y-x(mod L )这就是完完全全的第三种情况了,不过要保证k是正数,我们需要对k的范围作出限定,我们在求出特解之后在特解上进行加减的操作,保证k是恰好大于等于0的那一个解即可。这里的pair分别代表两只青蛙,pair的第一维是起始坐标,第二维是跳跃长度                                                 

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;

pair<long long,long long>A,B,ans;
long long L,a,b,g,d,x,y;
long long gcd(long long a,long long b){return a%b==0?b:gcd(b,a%b);}

bool flag;

void egcd(long long a,long long b,long long& d,long long& x,long long& y);

int main(){
    while(scanf("%lld%lld%lld%lld%lld",&A.first,&B.first,&A.second,&B.second,&L)!=EOF){

        if(A.second<B.second)
            swap(A,B);        //保证n-m是正数

        long long k=B.first-A.first;
        a=A.second-B.second;
        long long times;

        if(k<0){
            times=-k/L;
            k+=times*(L+1);
        }
        if(k%gcd(a,L)!=0){   //从这里开始就是求解第二种情况了
            printf("Impossible\n");
            continue;
        }
        egcd(a,L,d,x,y);
        x*=k/d;
        if(x<0){
            times=-x/(L/d);
            x+=(times+1)*(L/d);
        }
        else if(x>0){
            times=x/(L/d);
            x-=times*(L/d);
        }
        printf("%lld\n",x);
    }
    return 0;
}

void egcd(long long a,long long b,long long& d,long long& x,long long& y){
    (b==0)?(d=a,x=1,y=0):(egcd(b,a%b,d,y,x),y-=x*(a/b));
}

                       

                             POJ 2142   C Looooops  和上面的青蛙的题差不多,也是一个求解同余式的问题,在这里就是判断 A+ x*C ≡ B (mod (1<<k))这个方程是不是有解,做法和上面一样,也需要保证x是正数,在此不赘述。

 

#include<cstdio>

using namespace std;

typedef long long ll;
ll gcd(ll a,ll b){return a%b==0?b:gcd(b,a%b);};
ll a,b,c,d,k,x,y,times,mod;
void egcd(ll a,ll b,ll& d,ll& x,ll& y){(b==0)?(d=a,x=1,y=0):(egcd(b,a%b,d,y,x),y-=x*(a/b));};


int main(){
    while(scanf("%lld%lld%lld%lld",&a,&b,&c,&d)&&d){
        mod=1LL<<d;
        if(b-a<0)
            times=(a-b)/mod,k=(b-a)+(times+1)*mod;
        else
            k=b-a;
        if(k%gcd(c,mod)!=0){
            printf("FOREVER\n");
            continue;
        }
        egcd(c,mod,d,x,y);
        x*=k/d;
        if(x<0)
            times=-x/(mod/d),x+=(times+1)*(mod/d);
        else if(x>0)
            times=x/(mod/d),x-=times*(mod/d);
        printf("%lld\n",x);
    }
    return 0;
}

                      UVA 10090  Marbles     这道题目就是显然的第二种情况了,不过我们需要保证求出来的两个解都是正数的情况下的最小值。按照我们上面的通解公式,求解两个不等式x>0和y>0,得到k的范围,然后写出一个有关代价Cost的算术函数,然后分析在k的定义域内取得极值的条件就可以了。其实把代价函数写出来之后发现是一个有关于k的一次函数,但是斜率是和两个物品的性价比有关系,因此只需要额外拿出来讨论一下即可,总之是在两个端点取得极值(函数自己写吧,这里就不给出了)

#include<cstdio>
#include<utility>
#include<cmath>

using namespace std;
typedef long long ll;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);};
pair<ll,ll> pa,pb;
ll n,x,y,k,r,lcm,times,down,up;
ll egcd(ll a,ll b,ll& x,ll& y){return b==0?(x=1,y=0):(egcd(b,a%b,y,x),y-=x*(a/b));};

int main(){
    while(scanf("%lld",&n)&&n){
        scanf("%lld%lld%lld%lld",&pa.first,&pa.second,&pb.first,&pb.second);
        k=gcd(pa.second,pb.second);
        if(n%k!=0){
            printf("failed\n");
            continue;
        }
        egcd(pa.second,pb.second,x,y);
        r=n/k;lcm=pa.second*pb.second/k;
        x*=r,y*=r;
        down=ceil((pa.second*-x*1.0)/lcm),up=floor((y*1.0*pb.second)/lcm);
        if(down>up){
            printf("failed\n");
            continue;
        }
        if(pa.first*pb.second<=pb.first*pa.second)
            x+=up*lcm/pa.second,y-=up*lcm/pb.second;
        else
            x+=down*lcm/pa.second,y-=down*lcm/pb.second;
            printf("%lld %lld\n",x,y);
    }
    return 0;
}

                 POJ 2142  这道题目老实说我是分了三种情况来讨论的,一左一右有两种情况,还有一种就是两个都在左边的情况,方法和上面差不多,不过只是需要计算三种情况而已,一样的,在每种情况下分别解出通解x>0和y>0以及题目要求的(x+y)最小和(ax+by)最小,得出k的限定范围,然后在这三种情况里面取一个最优解即可。三种情况对应的方程分别是ax-by=d, by- ax =d和ax+by=d,注意一下通解的符号即可。(其实这里的第三种情况就是上面那一道题目。。。)这里的pa,pb,pc代表三种情况,pn代表答案


#include<cstdio>
#include<cmath>
#include<algorithm>

using namespace std;
typedef long long ll;

ll a,b,d,x,y,k,lcm,ans;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
void egcd(ll a,ll b,ll& x,ll& y){b==0?(x=1,y=0):(egcd(b,a%b,y,x),y-=x*(a/b));}
pair<ll,ll> pa,pb,pc,pn;
void judge();
bool flag=false;

int main(){
    while(scanf("%lld%lld%lld",&a,&b,&d)&&d){
        k=gcd(a,b),lcm=a*b/k;
        egcd(a,b,x,y);
        ans=ceil(max(y*1.0*b*d/lcm/k,-a*1.0*x*d/lcm/k));
        pa.first=d/k*x+ans*lcm/a,pa.second=-d/k*y+ans*lcm/b;

        ans=ceil(max(-b*y*1.0*d/k/lcm,a*x*1.0*d/k/lcm));
        pb.first=-d/k*x+ans*lcm/a,pb.second=d/k*y+ans*lcm/b;

        if(d>=a+b&&ceil(-a*x*d*1.0/k/lcm)<=floor(b*y*d*1.0/k/lcm)){
            flag=true;
            if(b-a<0)
                ans=floor(b*y*1.0*d/lcm/k);
            else if(b-a>0)
                ans=ceil(-a*x*d*1.0/lcm/k);
            pc.first=x*d/k+ans*lcm/a,pc.second=y*d/k-ans*lcm/b;
        }
        judge();
        printf("%lld %lld\n",pn.first,pn.second);
        flag=false;
    }
    return 0;
}

void judge(){
    if(pa.first+pa.second<pb.second+pb.first)
        pn=pa;
    else if(pa.first+pa.second>pb.second+pb.first)
        pn=pb;
    else
        pn=(a*pa.first+b*pa.second<a*pb.first+b*pb.second)?pa:pb;
    if(flag){
        if(pc.first+pc.second<pn.first+pn.second)
            pn=pc;
        else if(pc.first+pc.second==pn.first+pn.second&&a*pc.first+b*pc.second<pn.first*a+b*pn.second)
            pn=pc;
    }
}

                 UVA 10673    Play with Floor and Ceil  这道题真是太简单了。。。。既然任意解都可以的话。。通解是很明显的。。。因为floor(x)+1 == ceil(x) 在x不是整数情况下,答案很明显。。。

 

#include<cstdio>

using namespace std;
typedef long long ll;

ll t,a,b;

int main(){
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&a,&b);
        if(a%b!=0)
            printf("%lld %lld\n",-a,a);
        else
            printf("0 %lld\n",b);
    }
    return 0;
}


                     以上,拓展欧几里得例题讲解到此完毕,假若有函数不会写的也可以在下面留言。。其实我感觉只要把题目分析好了,就像个数学题。。重点是在草稿本上的推导。。



拓展欧几里得详解 及其题目 POJ 1061 2115 2142 UVA 10673 10090

标签:

原文地址:http://blog.csdn.net/good_night_sion_/article/details/52274156

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!