首页 > 其他 > 详细

某OJ 【扩展欧几里得】方程的解

时间:2019-07-22 16:40:09      阅读:116      评论:0      收藏:0      [点我收藏+]

技术分享图片

由于遭到警告,没敢粘题目,于是搞了个图片。


背景

  这题作为t1,还是蛮简单的

  其实刚开始我写这个题解的时候,我还没AC。【但是显然我最后A了

  但是烦的不行不想调了。

  反正我博客又没人看,于是就先写一写有关的知识点。

  其实我在考场上是想到了的,毕竟昨天偶然间翻到了一篇博客

  手玩了一下,发现确实是的。

  所以今天我其实本来很开心,觉得至少也有80分了【没考虑到一些细节对我来说很正常的

  然后快速的打完了这道题,才过了40分钟,信心满满的去做t2了。


思路分析

  这题一看 ax+by=c  

  正常人【指学过并且没忘的人 就应该能够意识到是扩展欧几里得。

  这里就简单复习一下:

  技术分享图片  听我讲!

  首先,扩展欧几里得就是求解  ax + by = c    的所有整数解的问题。

  ?????这两个有什么关系呢?

  这个时候,我们引入一个定理:裴蜀定理

    如果a、b是整数,那么一定存在整数x、y使得 ax+by=gcd(a,b)。

  不好理解的话,我们换一种说法:

      假设  ax+by=c  成立

      已知  ax+by=gcd(a,b)  

      所以  c一定是gcd(a,b)  的倍数

  这样的话,我们现在手里的方程就变成了一个具有gcd 的结果。

  那么是不是意味着我们只要能找到一组解,就能全部找出来

  好的,我们先感性理解一下,然后假装这个结论是对的。

  要找到一组解,那么就要找到gcd的值。

  想到求最大公因数,那么最先想到的就是辗转相除法

int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}

  所以,对于gcd而言,我们可以使用  gcd(a,b) = gcd(b, a%b)  这个式子一点一点推到底。

  直到b的值成为0,此时最大公因数就是a的值。

  好的,我们接下来取出这个过程中的两个式子(就拿第一个和第二个举例吧)

      a * x1 + b * y1 = gcd(a,b)
      b * x2 + (a%b) * y2 = gcd(b,a%b)

  这两个式子有什么关系呢?

  可以发现,只要能够建立起来x1与x2,y1与y2的关系,我们就可以得到一组解。

      a * x1 + b * y1 = b * x2 + (a%b) * y2

  ????我不会处理模的问题啊?怎么办呢?

  算法是死的,人是活的。

  我们可以尝试   把a%b换成a-(a/b)*b

  那么式子就变成了这样    a * x1 + b * y1 = b * x2 + (a - (a / b) * b) * y2

  解得  x1 = y2   y1 = x2 - (a / b) * y2 

  好的,那么这个问题就解决了。

  对于方程a * x + b * y = gcd(a,b),我们可以不断的往下变成b * x + (a % b) y = gcd(a,b) ;

  按照欧几里得的过程,b会变成0,即此时方程a * x + b * y = gcd(a,b)

  因为b = 0,变成了a * x = gcd(a,b) ,此时a就等于gcd(a,b)

  所以由原方程往下推了N次的方程 a * x = gcd(a,b) 来说,得到一个解x = 1, y = 0。

  所以如果得到了这个方程的解,再回溯回去,就可以得出原方程 a * x + b * y = gcd(a,b) 的一个特解。

现在我们要开始思考,怎么样才能通过一组解找到全部的解呢

  我们知道:

    对于关于x,y的方程a * x + b * y = c 来说,让x增加b/c,让y减少a/c,等式两边还相等。

  ????需要我解释一下吗?

  对于x来说,增加了b/c,那么加号左边就增加了a*b/c

  同样,对于y来说,减少了a/c,那么加号右边就减少了a*b/c

  显然是等价的,不影响等号成立。

  那么我们就可以通过增加和减少,逐步确定出所有的解。

  啊我好像知道自己为什么TLE了

  最后,有关a * x + b * y = gcd(a,b)这个方程,是一定有无数组解的。这就不用解释了吧。

  附上代码。

void exgcd(ll a,ll b,ll c,ll &x,ll &y,ll &g) {
    if(!b) {
        g=a;
        y=0;
        x=c/a;
    }
    else {
        exgcd(b,a%b,c,y,x,g);
        y-=(a/b)*x;
    }
    return;
}

本题思路

  这道题细节还是蛮多的,不然我也不会爆炸QAQ

  其实最有用的并不是exgcd,而是特判。特判全了之后,普通的gcd还有60分,不像我30分滚蛋

 

  细节问题:

    1、若a或b等于0 看不为零的是否整除c 若c为0 看a,b是否异号;  
    2、若两者为负就转化为一者为负; 
    3、若a,b异号,假设a<0,b>0 则循环x至多b次; 
    4、若a,b同号但不与c同号,则方程无解 由此若a,b,c均为负就可以全部化为正; 

  以上为特殊情况,答案只可能是0或者无穷多。

  以下情况中,a,b,c均为正数。 
    1、枚举x,从1开始,满足c-ax>=0,寻找某一时刻(c-ax)%b==0,则找到了方程的一组解; 
    2、这一组解一定是 x最小 y最大 的那组解,对于相邻的每两组解,y都减去了a和b的最小公倍数除以b,观察y能够减去多少个这个数; 
    再注意特判,注意处理负数情况即可。

  这些是我翻看了很多博客之后总结出来的,参考参考就ok。


 

代码实现

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;

void exgcd(ll a,ll b,ll c,ll &x,ll &y,ll &g) {
    if(!b) {
        g=a;
        y=0;
        x=c/a;
    }
    else {
        exgcd(b,a%b,c,y,x,g);
        y-=(a/b)*x;
    }
    return;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--) {
        ll a,b,c,x,y,sum,g;
        scanf("%lld%lld%lld",&a,&b,&c);
        if(!a&&!b) {
            if(!c)
                printf("ZenMeZheMeDuo\n");
            else 
                printf("0\n");
            continue;
        }
//        if(c<0) { 
//            if(a>0) 
//                aa=1;
//            else 
//                a=-a;
//            if(b>0) 
//                bb=1;
//            else 
//                b=-b;
//            c=-c;
//        }
        if(c<0) {//特判负数,系数全部转正
            a=-a;
            b=-b;
            c=-c;
        }
        bool aa=0;
        if(a<0) {
            a=-a;
            aa=1;
        } 
        bool bb=0;
        if(b<0) {
            b=-b;
            bb=1;
        }
        exgcd(a,b,c,x,y,g);//搞一组解 
        if(a*x+b*y!=c) {
            printf("0\n");
            continue;
        }
        if(aa) {
            x=-x;
            a=-a;
        } 
        if(bb) {
            y=-y;
            b=-b;
        }//搞回来负数 
        if(!a) {
            if(y>0)
                printf("ZenMeZheMeDuo\n");
            else 
                printf("0\n");
            continue;
        }
        if(!b) {
            if(x>0)
                printf("ZenMeZheMeDuo\n");
            else 
                printf("0\n");
            continue;
        }
        if((a<0&&b>0)||(a>0&&b<0)) {
            printf("ZenMeZheMeDuo\n");
            continue;
        }
        if(a<0) {
            a=-a;
            b=-b;
            c=-c;
        }
        a/=g;
        b/=g;
        c/=g; 
        x%=b;
        while(x<=0)
            x+=b;//必须是整数 
        y=(c-a*x)/b;
        ll y1=y%a;//最小值 
        while(y1<=0)
            y1+=a;
        int ans=0;
        if(y1>y) {
            printf("0\n");
            continue;
        }
        else 
            ans=(y-y1)/a+1;
        if(ans>65535)
            printf("ZenMeZheMeDuo\n");
        else 
            printf("%d\n",ans);
    }
    return 0;
}
/**
3
-1 -1 -3
1 1 65536
1 1 65537
**/

AC!!!!!!!!!!!

 

某OJ 【扩展欧几里得】方程的解

原文:https://www.cnblogs.com/qxyzili--24/p/11226220.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!