大一寒假训练五(GCD&&快速幂)【更新完成】

本次训练共10题,本文附AC代码和题目链接。

关于gcd的时间复杂度

gcd的复杂度最坏也不超过log2(n)

在《离散数学》上给出了一个证明(P77),可以证明,gcd的运行次数不会超过2*log2(n+1),n为较小数。

在上述证明的旁边,还给出了一个结论,拉梅定理:gcd的运行次数,不会超过较小数十进制位数的5倍,也就是5*log10(n)

(参考文章:https://blog.csdn.net/fengyuzhicheng/article/details/79561300)

gcd非递归模板

typedef long long ll;
ll gcd(ll a,ll b)
{
    ll r=a%b;
    while(r)
    {
        a=b;
        b=r;
        r=a%b;
    }
    return b;
}

gcd递归模板

long long gcd(long long a,long long b)
{return b?gcd(b,a%b):a;}

nefu 1411 LCM&GCD

假设gcd(a,b)=x,lcm(a,b)=y,则可得:gcd*lcm=a*b,即x*y=a*b
在[x,y]区间内暴力遍历所有a的取值,可以直接得到b的值b=x*y/a
若b在[x,y]范围内,并且gcd(a,b)==x,即满足条件,答案+1。
(事实上b一定在[x,y]范围内,因为a∈[x,y],又有b=x*y/a,则b∈[x,y])

这是时间复杂度O(t*n)的解法,时间1s的话肯定不能过。

#include 
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b)
{return b?gcd(b,a%b):a;}
ll t,x,y,ans;
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        cin>>x>>y;//假设gcd(a,b)=x,lcm(a,b)=y,则可得:gcd*lcm=a*b,即x*y=a*b
        ans=0;
        for(ll a=x;a<=y;a+=x)//在[x,y]区间内暴力遍历所有a的取值,a每次+x(比每次+1要快)
        //每次+x的原因是,由于x是a和b的最大公约数,则a一定是x的倍数,所以只需+x即可
        {
            if((x*y)%a==0)//满足a*b=x*y,则b=x*y/a,首先必须满足(x*y)%a==0
            {
                ll b=x*y/a;//直接得到b的取值
                if(gcd(a,b)==x)ans++;//gcd(a,b)==x,即满足条件
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

改进算法,O(t*sqrt(n))的解法:

假设gcd(a,b)=x,lcm(a,b)=y,则可得:gcd*lcm=a*b
即x*y=a*b,同除以x2,得y/x=(a/x)*(b/x)
令y1=y/x,a1=a/x,b1=b/x
则y1=a1*b1,且a1∈[1,y1]

这样化简之后,再遍历[1,sqrt(y1)](只需遍历到 根号y1 即可)找满足gcd(a1,b1)==1的情况,更新答案。

注意特判a1*a1=y的情况,答案+1;其他情况答案+2。

#include 
using namespace std;
typedef long long ll;
ll t,a,b,x,y,ans;
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    while(t--)
    {
        cin>>x>>y;
        ans=0;
        if(y%x!=0){printf("0\n");continue;}//最小公倍数不是最大公约数的倍数,直接输出0
        y=y/x;//将y缩小到y1
        for(a=1;a*a<=y;a++)//在[1,y1]区间内暴力遍历所有a1的取值,a每次+1
        {
            if(y%a==0)//满足y1=a1*b1,则b1=y1/a1,首先必须满足y1%a1==0
            {
                b=y/a;//直接得到b1的取值
                if(__gcd(a,b)==1)//gcd(a1,b1)==1则满足条件
                {
                    if(a*a==y)ans++;//特判a1*a1=y的情况,答案+1
                    else ans+=2;//其他情况答案+2
                }
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

nefu 1077 最大公约数和最小公倍数 (模板题)

#include 
using namespace std;
long long gcd(long long a,long long b)
{return b?gcd(b,a%b):a;}
long long lcm(long long a,long long b)
{return a/gcd(a,b)*b;}
int main()
{
    long long a,b;
    while(cin>>a>>b)
    {printf("%lld %lld\n",gcd(a,b),lcm(a,b));}
    return 0;
}

其实强大的C++内置了gcd函数!使用方法:__gcd(a,b)

#include 
using namespace std;
int main()
{
    long long a,b;
    while(cin>>a>>b)
        printf("%lld %lld\n",__gcd(a,b),a/__gcd(a,b)*b);
    return 0;
}

nefu 992 又见GCD

#include 
using namespace std;
int gcd(int a,int b)
{return b?gcd(b,a%b):a;}
int main()
{
    int a,b,i;
    while(cin>>a>>b)
    {
        for(i=b+1;;i++)
        {
            if(gcd(a,i)==b)
            {printf("%d\n",i);break;}
        }
    }
    return 0;
}

nefu 764 多个数的最大公约数

#include 
using namespace std;
long long gcd(long long a,long long b)
{return b?gcd(b,a%b):a;}
int main()
{
    long long n,i,a[11];
    while(cin>>n)
    {
        for(i=1;i<=n;i++)
            cin>>a[i];
        for(i=1;i<=n-1;i++)
            a[i+1]=gcd(a[i],a[i+1]);
        printf("%lld\n",a[n]);
    }
    return 0;
}

nefu 765 多个数的最小公倍数

#include 
using namespace std;
long long gcd(long long a,long long b)
{return b?gcd(b,a%b):a;}
long long lcm(long long a,long long b)
{return a/gcd(a,b)*b;}
int main()
{
    long long n,i,a[11];
    while(cin>>n)
    {
        for(i=1;i<=n;i++)
            cin>>a[i];
        for(i=1;i<=n-1;i++)
            a[i+1]=lcm(a[i],a[i+1]);
        printf("%lld\n",a[n]);
    }
    return 0;
}

nefu 1221 人见人爱gcd

这题要用数学公式推导出gcd(x,y)=gcd(a,b)
从而得到 x² + y² = a²-2 * b * gcd(a,b)

推导过程如下:
大一寒假训练五(GCD&&快速幂)【更新完成】_第1张图片
AC代码:

#include 
using namespace std;
int gcd(int a,int b)
{return b?gcd(b,a%b):a;}
int main()
{
    int t,a,b;
    ios::sync_with_stdio(false);
    while(cin>>t)
    {
        while(t--)
        {
            cin>>a>>b;
            printf("%d\n",a*a-2*b*gcd(a,b));
        }
    }
    return 0;
}

nefu 1669 高木同学的因子

#include 
using namespace std;
typedef long long ll;
ll x,y,i,ans;
int main()
{
    ios::sync_with_stdio(false);
    cin>>x>>y;
    ll k=__gcd(x,y);
    for(i=1;i*i<k;i++)
        if(k%i==0)ans+=2;
    if(i*i==k)ans++;
    printf("%lld\n",ans);
    return 0;
}

nefu 601 快速幂取模 (模板题)

#include 
using namespace std;
long long mode(long long a,long long b,long long mod)
{
    long long ans=1;
    while(b)
    {
        if(b%2==1)
        {b--;ans=ans*a%mod;}
        a=a*a%mod;
        b=b/2;
    }
    return ans;
}
int main()
{
    long long a,b,c;
    while(cin>>a>>b>>c)
    printf("%lld\n",mode(a,b,c));
    return 0;
}

nefu 1666 库特的数学题

打表找规律,可以发现 a[n]=6*3(n-1),用快速幂求解即可。

#include 
using namespace std;
typedef long long ll;
ll n,mod=1e9+7;
ll quickmod(ll a,ll b)
{
    ll s=1;
    while(b)
    {
        if(b&1)s=s*a%mod;
        a=a*a%mod;b=b/2;
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    printf("%lld\n",6*quickmod(3,n-1)%mod);//注意算出3的n-1次幂,再乘以6之后一定还要再取模!
    return 0;
}

nefu 1834 异或方程解的个数

移项n=(n⊕x)+x(⊕表示异或)

可以发现若n的某个二进制位为1,x有2种取值;若n的某个二进制位为0,x有1种取值。

所以只需要计算出n的二进制中1的个数,假设为num,答案就是2num

(不用快速幂也行,每次找到n中二进制位为1的地方就直接乘2即可)

AC代码:

#include 
using namespace std;
int qpow(int a,int b)
{
    int s=1;
    while(b)
    {
        if(b&1)s=s*a;
        b=b/2;
        a=a*a;
    }
    return s;
}
int main()
{
    int x,num;
    ios::sync_with_stdio(false);
    while(cin>>x)
    {
        num=0;
        while(x)
        {
            if(x&1)num++;
            x=x/2;
        }
        printf("%d\n",qpow(2,num));
    }
    return 0;
}

你可能感兴趣的:(ACM-基础算法/STL)