【数论数学】【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(a2,b)
2、a不是偶数,b是。那么gcd(a,b)=gcd(a,b2)
3、a,b都是偶数,那么gcd(a,b)=2 × gcd(a2,b2)
4、a,b都不是偶数,那么应用更损相减法,gcd(a,b)=gcd(b,ab)
考虑这么做的复杂度。当两个数是奇数的时候,需要更损相减法,两个奇数做差的答案是一个偶数。每次其中一个数除二后最多做一次更损相减。
考虑一个数除二的最大次数是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("\nemm");print(b);putchar('\n');
        if(a < b) {b-=a;}
        else {a-=b;}
    }
    ans=ans*a;
    print(ans);putchar('\n');
    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\n",k.len);
    for(rg int i=k.len;i>0;--i) putchar(k.num[i]+'0');
}

Summary

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

posted @   一扶苏一  阅读(493)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示