AcWing 200. Hankson的趣味题

AcWing 200. Hankson的趣味题

这道题 是姊妹题关系,套路应该是一样的,无脑的写代码:

一、题目描述

Hanks 博士是 BTBioTech,生物技术)领域的知名专家,他的儿子名叫 Hankson

现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题。

今天在课堂上,老师讲解了如何求两个正整数 c1c2 的最大公约数和最小公倍数。

现在 Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个 求公约数求公倍数 之类问题的 逆问题,这个问题是这样的:

已知正整数 a0,a1,b0,b1,设某未知正整数 x 满足:

  • xa0 的最大公约数是 a1
  • xb0 的最小公倍数是 b1

Hankson逆问题 就是求出满足条件的正整数 x

但稍加思索之后,他发现这样的 x 并不唯一,甚至可能不存在。

因此他转而开始考虑如何求解满足条件的 x 的个数。

请你帮助他编程求解这个问题。

输入格式
输入第一行为一个正整数 n,表示有 n 组输入数据。

接下来的 n 行每行一组输入数据,为四个正整数 a0a1b0b1,每两个整数之间用一个空格隔开。

输入数据保证 a0 能被 a1 整除,b1 能被 b0 整除。

输出格式
输出共 n 行。

每组输入数据的输出结果占一行,为一个整数。

对于每组数据:若不存在这样的 x,请输出 0

若存在这样的 x,请输出满足条件的 x 的个数;

数据范围
1n2000,1a0,a1,b0,b12109

输入样例

2
41 1 96 288
95 1 37 1776

输出样例

6
2

二、前置知识

以下性质,都是定义在整数范围内:

1、哪个数的约数最多,多少个?

其实就是求 反素数 这个题,共1600个。这个1600需要进行记忆,有很多题在定义上限时需要用到。

2、1N中任何数的不同质因子 个数 不会超过9

因为2×3×5×7×11×13×17×19×23×29>2×109:
一个数,它可以有很大的质数因子,但不同的质数因子个数,按最小的计算都无法超过9个,大点的就更不可能超过9个了,否则就超过了INT_MAX

3、枚举小质数因子需要到的上限值

  cout << sqrt(INT_MAX) << endl;

输出:46341,所以,一般开数组开到50000足够~

4、枚举最大公约数的倍数

枚举a1的倍数
TLE 6/11个数据

#include <bits/stdc++.h>
using namespace std;

// 最大公约数
int gcd(int x, int y) {
    return y ? gcd(y, x % y) : x;
}

// 最小公倍数
int lcm(int x, int y) {
    return y / gcd(x, y) * x; // 注意顺序,防止乘法爆int
}

int main() {
    // 输入
    int n;
    cin >> n;
    // 最大2000次噢
    while (n--) {
        /*
        读入四个数字
        x 和 a0 的最大公约数是 a1
        x 和 b0 的最小公倍数是 b1
        */
        int a0, a1, b0, b1;
        cin >> a0 >> a1 >> b0 >> b1;
        // 每次记数器清0
        int cnt = 0;
        // 枚举a1的所有倍数
        for (int x = a1; x <= b1; x += a1)
            if (gcd(x, a0) == a1 && lcm(x, b0) == b1) cnt++;

        printf("%d\n", cnt);
    }
    return 0;
}

5、枚举最小公倍数的约数

思考了下,为什么遍历倍数会大面积的TLE呢?
下载了测试点2的数据:

2000
21222 2 999993719 1999987438
9034 2 999978442 1999956884
24921 1 999975441 1999950882
...

b1很大,接近2e9a1很小,比如1,2,这样的极限数值,如果用在枚举倍数的时候,就会循环接近2e9次,不tle才是奇迹!说白了,就是出题人故意造成了些数据 卡掉了枚举倍数方法,铁了心 让我们使用枚举约数的方法

同时,我们也认识到,枚举约数可以只运算到1b1,数据量并不大,性能有保障,以后还是记住套路,尽量枚举最小公倍数的约数,这样更靠谱些。

时间复杂度: O(nb1)

由于 [x,b0]=b1,因此 x 一定是 b1 的约数。
所以我们可以枚举 b1 的所有约数,然后依次判断是否满足 [x,b0]=b1 以及 (x,a0)=a1 即可。

如果直接用试除法求 b1 的所有约数,那么总计算量是
nb1=20002×109108,会有一个测试数据超时。

#include <bits/stdc++.h>
using namespace std;

// 最大公约数
int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

// 最小公倍数
int lcm(int a, int b) {
    return b / gcd(a, b) * a; // 注意顺序,防止乘法爆int
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        int ans = 0, a0, a1, b0, b1;
        cin >> a0 >> a1 >> b0 >> b1;
        /*
            读入四个数字
            x 和 a0 的最大公约数是 a1
            x 和 b0 的最小公倍数是 b1
        */
        for (int i = 1; i * i <= b1; i++) {                  // 枚举b1的所有约数
            if (b1 % i) continue;                            // 是因数
            if (gcd(i, a0) == a1 && lcm(i, b0) == b1) ans++; // 因数i符合要求
            int j = b1 / i;                                  // 另一个因子
            if (gcd(j, a0) == a1 && lcm(j, b0) == b1 && i != j) ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}

五、优化思路

上面的代码,之所以最后两个测试点TLE,根本的原因在于1n的因数试除!
有的不可能的因数,也进行了试除,需要O(N)的时间复杂度,这个是慢的原因。

注:好神奇,20231114日再次看这道题时,发现上面的代码就可以直接AC了,不需要再优化了~

下面尝试对这个O(n)的算法想办法进行优化:

那么我们应该如何 快速求出n的所有约数

  • ① 欧拉筛 筛出150000之间的所有质数(因为500002>2×109

  • ② 利用上面的所有质数数组,将b1分解质因数,生成b1质数因数有哪些,并且,每个质数因数有几个

    for (int i = 0; primes[i] <= t / primes[i]; i++) {
        int p = primes[i];
        if (t % p == 0) {
            int s = 0;
            while (t % p == 0) t /= p, s++;
            f[fl++] = {p, s}; //记录小质数因子和个数
        }
    }
    
  • ③ 现在得到的只是质数因子的信息,并不是我们想要的约数信息,还需要进行转化,怎么转化呢?使用dfs!
    举个栗子:24=2331,约数有(1,2,3,4,6,8,12,24),可以视为

    • 1=2030
    • 2=2130
    • 3=2031
    • 4=2231
    • 6=2131
    • 8=2330
    • 12=2231
    • 24=2331
      我们用dfs方式枚举所有的组合情况,就可以得到24的所有约数数组!
/**
功能:根据分解完成的质数因子数组 获取 所有约数
u:走到已经求出的质数因子数组f面前,现在是第u个
p: 已经拼接完成的的约数,初始值是1,是0的话没法通过质数因子相乘得到结果,base=1
*/
void dfs(int u, int p) {
    if (u == fl) {   // 如果所有质数因子遍历完成 0~fl-1是所有质因子的下标
        d[dl++] = p; // 约数又多了一个
        return;
    }

    // 枚举当前质数因子f[u]使用几个,最少是0个,最多是f[u].count个
    for (int i = 0; i <= f[u].count; i++) {
        dfs(u + 1, p);
        p *= f[u].prime; // 这两句话用的太漂亮了,完美的模拟了要0个,要1个,要2个...牛B plus!
    }
}

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
typedef long long LL;

struct Node {
    int prime; // 质数因子
    int count; // 个数
} f[10];       // 一维:哪个质数因子,二维:有几个 f:因子
// 根据经验, primes[]={2,3,5,7,11,13,17,19,23}足够分解INT_MAX,共9个就够了
int fl; // 配合数组使用的游标

int d[1610], dl; // 约数数组,约数数组游标 d:约数
// 根据经验INT_MAX中约数个数最多的是1600个,开1610足够。

// 欧拉筛
int primes[N], cnt; // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉
void get_primes(int n) {
    memset(st, 0, sizeof st);
    cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) primes[cnt++] = i;
        for (int j = 0; primes[j] * i <= n; j++) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

// 最大公约数,辗转相除法
int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

// 最小公倍数
int lcm(int a, int b) {
    return b / gcd(a, b) * a; // 注意顺序,防止乘法爆int
}

/**
 功能:根据分解完成的质数因子数组 获取 所有约数
 u:走到已经求出的质数因子数组f面前,现在是第u个
 p: 已经拼接完成的的约数,初始值是1,是0的话没法通过质数因子相乘得到结果,base=1
 */
void dfs(int u, int p) {
    if (u == fl) {   // 如果所有质数因子遍历完成 0~fl-1是所有质因子的下标
        d[dl++] = p; // 约数又多了一个
        return;
    }

    // 枚举当前质数因子f[u]使用几个,最少是0个,最多是f[u].count个
    for (int i = 0; i <= f[u].count; i++) {
        dfs(u + 1, p);
        p *= f[u].prime; // 这两句话用的太漂亮了,完美的模拟了要0个,要1个,要2个...牛B plus!
    }
}

int main() {
    get_primes(50000); // 求小的质数因子,sqrt(INT_MAX)<50000,开50000很保险

    int n;
    cin >> n;
    while (n--) {
        /*
            读入四个数字
            x 和 a0 的最大公约数是 a1
            x 和 b0 的最小公倍数是 b1
        */
        int a0, a1, b0, b1;
        cin >> a0 >> a1 >> b0 >> b1;

        fl = 0;     // 多组数据,每次注意清零
        int t = b1; // 拷贝出来,一直除到没有为止

        // 枚举b1的每个质数小因子
        for (int i = 0; primes[i] <= t / primes[i]; i++) {
            int p = primes[i];
            if (t % p == 0) {
                int s = 0;
                while (t % p == 0) t /= p, s++;
                f[fl++] = {p, s}; // 记录小质数因子和个数
            }
        }

        // 如果存在大的质因子,那么最多只有一个,比如2*7=14中的7,此时t=7
        // 也可能b1本身就是一个质数,比如131,那么此时t=131
        if (t > 1) f[fl++] = {t, 1}; // 记录到质数数组中

        // 现在求出的是b1的所有质数因数,题目要求的是约数,利用dfs通过质数因子获取所有约数
        dl = 0;    // 多组测试数据,也清一下零吧!
        dfs(0, 1); // 一次dfs,将质数因子数组  转换 约数数组,p的默认值是1

        int res = 0;                   // 答案数量
        for (int i = 0; i < dl; i++) { // 枚举所有约数
            int x = d[i];              // 判断是不是符合题意
            if (gcd(a0, x) == a1 && lcm(b0, x) == b1) res++;
        }
        // 输出结果
        printf("%d\n", res);
    }

    return 0;
}
posted @   糖豆爸爸  阅读(257)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2018-08-30 关于南京市智慧教育基础平台市区协同推进项目申报工作的通知
2018-08-30 Gitlab库已损坏前端显示500错误解决方法
2017-08-30 服务器老是出现502 Bad Gateway?
2017-08-30 云平台服务运行情况检测脚本V0.1
2014-08-30 JAVA版数据库主键ID生成器
Live2D
点击右上角即可分享
微信分享提示