• 【数论数学】【P2152】【SDOI2009】Super GCD


    传送门

    Description

    Sheng bill有着惊人的心算能力,甚至能用大脑计算出两个巨大的数的GCD(最大公约 数)!因此他经常和别人比赛计算GCD。有一天Sheng bill很嚣张地找到了你,并要求和你比 赛,但是输给Sheng bill岂不是很丢脸!所以你决定写一个程序来教训他。

    Input

    共两行: 第一行:一个数A。 第二行:一个数B。

    Output

    一行,表示A和B的最大公约数。

    Sample Input

    12
    54
    

    Sample Output

    6
    

    Hint

    对于100%的数据,0 < A , B ≤ 10 ^ 10000。

    Solution

    如果你觉得这是个裸的gcd的话,你会发现高精度乘除取余在这么大的位数下就是找死。考虑使用高精度运算复杂度更低的更损相减法。但是朴素的更损相减法是(O(n))的。需要进行优化。
    考虑对于两个数(a,b)两数共可能出现以下情况:
    (不妨设(a>b)
    1、(a)是偶数,(b)不是。那么(gcd(a,b)=gcd(frac{a}{2},b))
    2、(a)不是偶数,(b)是。那么(gcd(a,b)=gcd(a,frac{b}{2}))
    3、(a,b)都是偶数,那么(gcd(a,b)=2~ imes~gcd(frac{a}{2},frac{b}{2}))
    4、(a,b)都不是偶数,那么应用更损相减法,(gcd(a,b)=gcd(b,a-b))
    考虑这么做的复杂度。当两个数是奇数的时候,需要更损相减法,两个奇数做差的答案是一个偶数。每次其中一个数除二后最多做一次更损相减。
    考虑一个数除二的最大次数是(logn)的。所以优化后更损相减部分的复杂度是(O(logn))的。

    Code

    // luogu-judger-enable-o2
    #include<cstdio>
    #include<cstring> 
    #define rg register
    #define ci const int
    #define cl const long long int
    
    typedef long long int ll;
    
    namespace IO {
        char buf[50];
    }
    
    template<typename T>
    inline void qr(T &x) {
        char ch=getchar(),lst=' ';
        while(ch>'9'||ch<'0') lst=ch,ch=getchar();
        while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        if (lst=='-') x=-x;
    }
    
    template<typename T>
    inline void write(T x,const char aft,const bool pt) {
        if(x<0) {putchar('-');x=-x;}
        int top=0;
        do {
            IO::buf[++top]=x%10+'0';
            x/=10;
        } while(x);
        while(top) putchar(IO::buf[top--]);
        if(pt) putchar(aft);
    }
    
    template <typename T>
    inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
    template <typename T>
    inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
    template <typename T>
    inline T mabs(const T a) {if(a<0) return -a;return a;}
    
    template <typename T>
    inline void mswap(T &a,T &b) {T temp=a;a=b;b=temp;}
    
    struct Bignum {
        short int num[10010],len;
        void clear() {memset(num,0,sizeof num);len=0;}
        void operator-=(const Bignum &others) {
            for(rg int i=1;i<=this->len;++i) {
                this->num[i]-=others.num[i];
                while(this->num[i]<0) {
                    this->num[i]+=10,--this->num[i+1];
                }
            }
            while(!this->num[this->len]) --this->len;
            if(!this->len) ++this->len;
        }
        bool operator!=(const Bignum &others) {
            if(this->len!=others.len) return true;
            for(rg int i=1;i<=this->len;++i) if(this->num[i] != others.num[i]) return true;
            return false;
        }
        bool operator<(const Bignum &others) {
            if(this->len!=others.len) return this->len<others.len;
            for(rg int i=len;i;--i) if(this->num[i]!=others.num[i]) return this->num[i]<others.num[i];
            return false;
        }
        Bignum operator*(const Bignum &others) {
            Bignum _ans;_ans.clear();
            int llen=this->len+others.len+5;
            for(rg int i=1;i<=this->len;++i) {
                for(rg int j=1;j<=others.len;++j) {
                    _ans.num[i+j-1]+=this->num[i]*others.num[j];
                }
            }
            for(rg int i=1;i<=llen;++i) {
                _ans.num[i+1]+=_ans.num[i]/10;
                _ans.num[i]%=10;
            }
            _ans.len=llen;
            while(!_ans.num[_ans.len]) --_ans.len;
            if(!_ans.len) _ans.len=1;
            return _ans;
        }
    };
    Bignum a,b,ans;
    
    char MU[10010];
    int cnt;
    
    void dv(Bignum&);
    void mul(Bignum&);
    void init(Bignum&,int);
    void print(Bignum&);
    
    int main() {
        scanf("%s",MU+1);init(a,strlen(MU+1));
        scanf("%s",MU+1);init(b,strlen(MU+1));
        ans.num[1]=1;ans.len=1;
        while(a != b) {
            bool flag=false;
            if(!((int(a.num[1])) & 1)) {dv(a);flag=true;}
            if(!((int(b.num[1])) & 1)) {dv(b);if(flag) mul(ans);flag=true;}
            if(flag) continue;
        //	print(a);puts("
    emm");print(b);putchar('
    ');
            if(a < b) {b-=a;}
            else {a-=b;}
        }
        ans=ans*a;
        print(ans);putchar('
    ');
        return 0;
    }
    
    void init(Bignum &k,int l) {
        for(rg int i=l;i;--i) k.num[++k.len]=MU[i]-'0';
    }
    
    void dv(Bignum &k) {
        int lst=0;
        for(rg int i=k.len;i;--i) {
            int _temp=k.num[i]+(lst<<1)+(lst<<3);
            int _ans=_temp/2;
            if(_temp & 1) lst=1;else lst=0;
            k.num[i]=_ans;
        }
        while(!k.num[k.len]) --k.len;
        return;
    }
    
    void mul(Bignum &k) {
        int lst=0;k.len+=2;
        for(rg int i=1;i<=k.len;++i) {
            k.num[i]*=2;
            k.num[i]+=lst;
            lst=k.num[i]/10;k.num[i]%=10;
        }
        while(!k.num[k.len]) --k.len;
        if(!k.len) ++k.len;
        return;
    }
    
    void print(Bignum &k) {
    //	printf("%d
    ",k.len);
        for(rg int i=k.len;i>0;--i) putchar(k.num[i]+'0');
    }
    

    Summary

    在高精度运算下求gcd,如果两数特大可以考虑使用优化后的更损相减。但是需要注意的是更损相减法的常数在极端情况下大概会达到(4)倍。而欧几里得算法的常数小于(1)。在取模计算量可以忽略的情况下应尽量选择欧几里得算法。
    再说一句GCD真的不是什么党……

  • 相关阅读:
    汽车过沙漠问题【学习笔记】
    极值问题
    双色汉诺塔【分离型】
    二分查找(递归与非递归)
    t2712:字符串移位包含问题
    酒瓶和瓶盖换购新酒
    反转诗句
    字符串的展开
    回文子串[输出所有回文子串]
    最长最短单词
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/9570908.html
Copyright © 2020-2023  润新知