数论:最大公约数和最小公倍数的应用:Hankson的趣味题

本文介绍了如何利用数学知识和编程技巧解决与约数和公倍数相关的算法问题,包括如何高效求解一个数的约数个数,以及最大公约数和最小公倍数的计算。文章通过实例解析了如何确定两个数的公共因子和倍数,并提供了两种不同的解题方法,包括直接枚举和利用质因数分解。此外,还提供了AC代码示例,适用于处理大规模数据的求解策略。
摘要由CSDN通过智能技术生成

总结:

1.如果lcm(b,x) == d,(b,x的最小公倍数 == d), 则应该知道, b, x 都为d的约数。

2.而当b,x都为d的约数的时候,则b 以及 x 中的 质因子, 在 d这个最小公倍数中都存在。

3.约数的个数:n的约数个数的上界为sqrt(n),也就是最多为sqrt(n)个约数,但是实际上,1~n中平均下来的话,每个数的约数大概只有(log n)个。

4.在1e9以内,约数个数最多个值它的约数也就1536个约数

5.由3,4就可以知道,如果有多组测试数据,求n的约数, 实际上我们可以先预处理出 sqrt(max(n) ), 根据题目的要求,n最大的取值下的sqrt(n)的质数全部筛出来.

然后直接对n 用 预处理出的质数   筛出对应于当前n的各个质因数 以及 指数。(实际上按照之前反素数那道题目所说,实际上n的质因数不会超过11个。题解链接:反素数题解

而筛出了对应的质因数 以及 指数 以后,直接通过dfs()算出各个约数,存起来即可。

6. 同时最大公约数 以及 最小公倍数中还会常用质因数 与它两之间的关系进行求解。(之前的最大公约数和最小公倍数的问题就用到了这个方式进行求解)。题解链接:https://blog.csdn.net/qq_49120553/article/details/119078625?spm=1001.2014.3001.5502

如果a的素因数分解为 (x1 ^ p1) * (x2 ^ p2) * (x3 ^ p3)

b的素因数分解为 (x1 ^ p4) * (x5 ^ p5) * (x6 ^ p6)

可以增添一下素因数a : (x1 ^ p1) * (x2 ^ p2) * (x3 ^ p3) *(x5 ^ 0) * (x6 ^ 0)

b:(x1 ^ p4) * (x5 ^ p5) * (x6 ^ p6)* (x2 ^ 0) * (x3 ^ 0)

则a,b的最大公约数为:(x1 ^ min(p1,p4) ) * (x2 ^ min (p2 ,0) ) * (x3 ^ min(p3,0) ) * (x5 ^ min(0,p5)) * (x6 ^ min(0.p6) ).

a,b的最小公倍数为:(x1 ^ max(p1,p4) ) * (x2 ^ max (p2 ,0) ) * (x3 ^ max(p3,0) ) * (x5 ^ max(0,p5)) * (x6 ^ max(0.p6) ).

题目链接:https://ac.nowcoder.com/acm/problem/16610 

题目:

题目描述

Hanks博士是BT(Bio-Tech,生物技术)领域的知名专家,他的儿子名叫Hankson。现在,刚刚放学回家的Hankson正在思考一个有趣的问题。

今天在课堂上,老师讲解了如何求两个正整数c1和c2的最大公约数和最小公倍数。现在Hankson认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数a0,a1,b0,b1,设某未知正整数x满足:

1、 x和a0的最大公约数是a1;

2、 x和b0的最小公倍数是b1。

Hankson的“逆问题”就是求出满足条件的正整数x。但稍加思索之后,他发现这样的x并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的x的个数。请你帮助他编程求解这个问题。

输入描述:

 
 

第一行为一个正整数n,表示有n组输入数据。接下来的n行每行一组输入数据,为四个正整数a0,a1,b0,b1,每两个整数之间用一个空格隔开。输入数据保证a0能被a1整除,b1能被b0整除。

输出描述:

对于每组数据:若不存在这样的x,请输出0;
若存在这样的x,请输出满足条件的x的个数;

示例1

输入

复制2 41 1 96 288 95 1 37 1776

2
41 1 96 288
95 1 37 1776

输出

复制6 2

6
2

说明

第一组输入数据,x可以是9、18、36、72、144、288,共有6个。
第二组输入数据,x可以是48、1776,共有2个。

备注:

 
 

对于50%的数据,保证有1≤a0,b1,b0,b1≤10000且n≤100。

对于100%的数据,保证有1≤a0,b1,b0,b1≤2,000,000,000且n≤2000。

分析1:

 由结论可以知道,lcm(x,b0) == b1,则x为b1的约数。

那么我们可以直接去找b1的所有约数,然后去判断这个约数x是否满足我们的条件(gcd(x,a0) == a1 && lcm(x,b0) == b1)

如果满足条件,那么就统计值total++。最后输出即可。

但是,要注意,如果直接使用试除法求是否为约数的话,时间复杂度会变为n*sqrt(b1) * log b1. 会超时。

所以就需要用到我们总结中的另一个求某个数约数的技巧,先将最大值的sqrt(n)内的质数筛出来。(就像之前的筛质因数一样,先筛sqrt(n),最后剩下的就是唯一一个大于sqrt(n)的值)。 然后通过筛出的质数,筛出sqrt(n)内满足条件的质数 和 它的指数。

最后通过dfs()求解出 其 对应的所有约数即可。(减少不能整除的情况)

 代码实现:

90的代码:

# include <iostream>
using namespace std;
 
int gcd(int a , int b)
{
    return b ? gcd(b,a % b) : a;
}
 
int main()
{
    int loop;
    scanf("%d",&loop);
    while(loop--)
    {
        int a0,a1,b0,b1;
        scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
        int cnt = 0;
        for(int i = 1 ; i <= b1 / i ; i++)
        {
            if(b1 % i == 0)
            {
                int temp1 = i;
                if(gcd(temp1,a0) == a1 && (long long)gcd(temp1,b0) * b1 == (long long)temp1 * b0)
                {
                    cnt++;
                }
                if(b1 / i != i)
                {
                    temp1 = b1 / i;
                }
                if(gcd(temp1,a0) == a1 && gcd(temp1,b0) * b1 == temp1 * b0)
                {
                    cnt++;
                }
            }
        }
        printf("%d\n",cnt);
    }
    return 0;
}

ac代码:

# include <iostream>
# include <vector>
using namespace std;
 
const int N = 2e5 + 10;
 
int prim[N],cnt;
bool choose[N];
 
typedef pair<int,int> PII;
vector<PII> p;
 
int t; // 统计约数的个数
int yueshu[N];
 
int gcd(int a , int b)
{
    return b ? gcd(b,a % b) : a;
}
 
void get_prim(int n)
{
    for(int i = 2 ; i <= n ; i++)
    {
        if(!choose[i])
        {
            prim[++cnt] = i;
        }
        for(int j = 1 ; prim[j] <= n / i ; j++)
        {
            choose[i * prim[j]] = true;
            if(i % prim[j] == 0)
            {
                break;
            }
        }
    }
}
 
void dfs(int u , int i)
{
    if(i == p.size())
    {
        yueshu[++t] = u;
        return;
    }
    for(int j = 0 ; j <= p[i].second ; j++)
    {
        dfs(u, i + 1);
        u = u * p[i].first; // 最后会多乘一次u,但是不影响,因为最后一个u没有用处
    }
}
 
int main()
{
    get_prim(N); // 对前 sqrt(2e9)筛质数进行预处理。
     
    int loop;
    scanf("%d",&loop);
    while(loop--)
    {
        p.clear();
        t = 0;
         
        int a0,a1,b0,b1;
        scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
         
        int d = b1;
        //求b1的约数,先求出b1对应的质数,以及其对应的质数
        for(int i = 1 ; prim[i] <= d / prim[i] ; i++) // 只需要先筛出 前sqrt(b1)对应的值,最后一个值就是大于sqrt(b1)的值了
        {
            if(d % prim[i] == 0)
            {
                int time = 0;
                while(d % prim[i] == 0)
                {
                    time++;
                    d /= prim[i];
                }
                p.push_back({prim[i],time});
            }
        }
        if(d > 1)  //最后剩下的就是大于sqrt(b1)的值了
        {
            p.push_back({d,1});
        }
        
        //上面一步将所有的b1的质数 以及 指数都整出来了。接下来求其对应的约数
         
        dfs(1,0); // dfs(u,i) u代表当前的值,i代表到了第几个指数
         
        //上面的dfs()整理出了所有的约数
        int count = 0;  // 求满足条件的个数
         
        for(int i = 1 ; i <= t ; i++)
        {
            int x = yueshu[i];
            if (gcd(x, a0) == a1 && (long long)x * b0 / gcd(x, b0) == b1)
            {
                count++ ;
            }
        }
        printf("%d\n",count);
    }
    return 0;
}

分析2:

还有另一种方式,就是用我们总结6,gcd()和lcm()与质因数之间的关系进行求解。

由我们的分析1已经知道,x为d1的约数。 所以x中的质因数,在d1中必定全部都有。

所以我们算出d1的各个质因数 以及其 指数,以及 a0,a1的有关 d1质因数 以及对应的指数。

根据gcd()和lcm()与质因数之间的关系,总结出对应的关系。

 其中每个所对应的无解情况,就是对应的a,b,c,d,取值是非法的,凑不出对应的x出来。

对应的值为0.

如果没有无解的情况,随后通过乘法原理将各个d1对应质因数下对应的值求解出最后的答案。

# include <iostream>
# include <vector>
using namespace std;
 
const int N = 2e5 + 10;
 
int prim[N],cnt;
bool choose[N];
 
void get_prim(int n)
{
    for(int i = 2 ; i <= n ; i++)
    {
        if(!choose[i])
        {
            prim[++cnt] = i;
        }
        for(int j = 1 ; prim[j] <= n / i ; j++)
        {
            choose[i * prim[j]] = true;
            if(i % prim[j] == 0)
            {
                break;
            }
        }
    }
}
 
int main()
{
    get_prim(N); // 对前 sqrt(2e9)筛质数进行预处理。
 
    int loop;
    scanf("%d",&loop);
    while(loop--)
    {
        int a0,a1,b0,b1;
        scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
        int a = a0;
        int c = a1;
        int b = b0;
        int d = b1;
         
         
        int count = 1;
        bool flag = true;
        for(int i = 1 ; prim[i] <= d / prim[i] ; i++)
        {
            int ma = 0,mc = 0,mb = 0,md = 0;
            if(d % prim[i] == 0)
            {
                while(a % prim[i] == 0)
                {
                    ma++;
                    a /= prim[i];
                }
                while(c % prim[i] == 0)
                {
                    mc++;
                    c /= prim[i];
                }
                while(b % prim[i] == 0)
                {
                    mb++;
                    b /= prim[i];
                }
                while(d % prim[i] == 0)
                {
                    md++;
                    d /= prim[i];
                }
                if(ma == mc && mb == md && mc <= md)
                {
                    count *= (md - mc + 1);
                     
                }
                else if(ma > mc && mb < md && mc == md)
                {
                    continue;
                }
                else if(ma > mc && mb == md && mc <= md)
                {
                    continue;
                }
                else if(ma == mc && mb < md && mc <= md)
                {
                    continue;
                }
                else
                {
                    flag = false;
                    break;
                }
            }
             
        }
        if(flag && d > 1)
        {
            int ma = 0,mc = 0,mb = 0,md = 1;
            while(a % d == 0)
            {
                ma++;
                a /= d;
            }
            while(c % d == 0)
            {
                mc++;
                c /= d;
            }
            while(b % d == 0)
            {
                mb++;
                b /= d;
            }
            if(ma == mc && mb == md && mc <= md)
            {
                count *= (md - mc + 1);
            }
            else if(ma > mc && mb < md && mc == md)
            {
                count *= 1;
            }
            else if(ma > mc && mb == md && mc <= md)
            {
                count *= 1;
            }
            else if(ma == mc && mb < md && mc <= md)
            {
                count *= 1;
            }
            else
            {
                flag = false;
            }
        }
         
        if(flag)
        {
            printf("%d\n",count);
        }
        else
        {
            printf("0\n");
        }
         
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值