题解:计算约数个数

题目理解

核心要求:给定多个正整数,计算并输出每个数的约数(因数)个数。

输入格式

  • 第一行:正整数N(1 ≤ N ≤ 1000),表示需要处理的数字个数
  • 第二行:N个正整数,每个数的范围是1 ≤ Num ≤ 10^9

输出格式

  • 对于每组输入数据,输出N行
  • 每行对应输入中相应位置数字的约数个数

关键约束条件

  • 数字范围较大(最大10^9),需要高效的算法
  • 可能存在多组测试数据
  • 需要处理完全平方数的特殊情况

解题思路

直觉思路(暴力方法)

最直接的想法是遍历从1到x的所有数字,检查是否能整除:

复制代码
int count = 0;
for(int i = 1; i <= x; i++) {
    if(x % i == 0) count++;
}

问题:当x = 10^9时,需要循环10^9次,时间复杂度O(x),会超时。

优化思路

核心观察:约数是成对出现的。如果i是x的约数,那么x/i也是x的约数。

例如:24的约数有1, 2, 3, 4, 6, 8, 12, 24

  • 1 × 24 = 24
  • 2 × 12 = 24
  • 3 × 8 = 24
  • 4 × 6 = 24

优化策略:只需要遍历到√x即可,因为超过√x的约数都可以通过配对得到。

最终解法

  1. 遍历i从1到√x
  2. 如果i能整除x,说明i和x/i都是约数,计数+2
  3. 特殊处理:如果x是完全平方数(√x × √x = x),那么√x只算一个约数

正确性证明

  • 对于任意约数d,必然存在配对约数x/d
  • 当d < √x时,x/d > √x,不会重复计数
  • 当d = √x时,d = x/d,只能计数一次
  • 当d > √x时,已经在前面的循环中被x/d计数过了

算法设计

使用的算法:数学优化 + 枚举

关键步骤

  1. 计算x的平方根:int n = sqrt(x)
  2. 遍历1到√x-1(注意边界)
  3. 对每个能整除的i,计数+2
  4. 检查是否为完全平方数,如果是则计数+1

数据结构:无需特殊数据结构,使用基本变量即可

代码解析

ans函数详解

复制代码
int ans(int x){
    int count=0;              // 初始化约数计数器
    int n=sqrt(x);            // 计算x的平方根,用于优化循环次数
    
    // 遍历从1到√x-1(注意i*i < x,不包含等于的情况)
    for(int i=1; i*i < x; i++){
        if(x % i == 0){       // 如果i能整除x
            count += 2;       // i和x/i都是约数,计数+2
        }
    }
    
    // 特殊处理完全平方数
    if(n * n == x){           // 如果x是完全平方数
        count++;              // √x只能算一个约数
    }
    
    return count;
}

难点和易错点

  1. 循环边界i*i < x 而不是 i <= sqrt(x)

    • 使用 i*i < x 避免了浮点数精度问题
    • 如果使用 i <= sqrt(x),浮点数比较可能产生误差
  2. 完全平方数处理

    • 必须单独处理,否则会漏掉√x这个约数
    • 例如:16的约数是1,2,4,8,16,其中4只出现一次
  3. 计数逻辑

    • 每次找到一个约数i,同时找到配对约数x/i
    • 但完全平方数的√x不能重复计数

main函数详解

复制代码
int main() {
    int n;
    while (cin >> n) {        // 处理多组测试数据
        while (n--) {         // 对每组数据中的每个数字
            int x;
            cin >> x;
            cout << ans(x) << endl;  // 输出约数个数
        }
    }
    return 0;
}

设计要点

  • 使用while(cin >> n)处理多组输入
  • 内层循环处理每组数据中的所有数字
  • 对每个数字调用ans函数计算约数个数

复杂度分析

时间复杂度

单个数字的计算:O(√x)

  • 循环次数最多为√x次
  • 当x = 10^9时,√x = 31622,远小于10^9

整体复杂度:O(N × √x_max)

  • N为数字个数(≤ 1000)
  • x_max为最大数字(≤ 10^9)
  • 最坏情况下:1000 × 31622 ≈ 3.16 × 10^7次操作,可接受

空间复杂度

O(1):只使用了常数个变量,无需额外存储空间

优化与扩展

可能的优化

  1. 质因数分解法(更优解)

    • 如果x = p₁^a₁ × p₂^a₂ × ... × pₖ^aₖ
    • 约数个数 = (a₁+1) × (a₂+1) × ... × (aₖ+1)
    • 时间复杂度:O(√x)用于质因数分解,但常数更小
    • 适合多次查询相同数字的场景
  2. 预处理优化

    • 如果数字范围较小,可以预处理所有数字的约数个数
    • 使用筛法思想,时间复杂度O(N log N)

推广应用

  1. 判断完全数:如果约数和等于2x(包括自身)
  2. 判断亲和数:两个数的约数和相等
  3. 计算约数和:类似思路,累加约数而非计数
  4. 数论问题:欧拉函数、莫比乌斯函数等

边界情况测试

复制代码
// 测试用例
ans(1)   = 1    // 只有1本身
ans(2)   = 2    // 1, 2
ans(4)   = 3    // 1, 2, 4(完全平方数)
ans(12)  = 6    // 1, 2, 3, 4, 6, 12
ans(100) = 9    // 1, 2, 4, 5, 10, 20, 25, 50, 100

总结

本题通过数学观察(约数成对出现)将时间复杂度从O(x)优化到O(√x),是典型的数学优化问题。关键在于:

  1. 理解约数的对称性:利用i和x/i的配对关系
  2. 处理边界情况:完全平方数的特殊处理
  3. 避免精度问题:使用i*i < x而非浮点数比较

这种思路在数论问题中非常常见,掌握后可以解决很多类似的约数相关问题。

相关推荐
图码2 小时前
递归入门:从n到1的优雅打印之旅
数据结构·c++·算法·青少年编程·java-ee·逻辑回归·python3.11
ximu_polaris2 小时前
设计模式(c++)-结构型模式-装饰器模式
c++·设计模式·装饰器模式
Queenie_Charlie2 小时前
二叉树_
c++·二叉树·简单树结构
生信之灵2 小时前
拓扑与曲率双剑合璧:scGeom如何从单细胞数据中“看见”细胞命运
人工智能·深度学习·算法·单细胞·多组学
良木生香2 小时前
【C++初阶】:STL——String从入门到应用完全指南(3)
c语言·开发语言·数据结构·c++·算法
_深海凉_2 小时前
LeetCode热题100-在排序数组中查找元素的第一个和最后一个位置
算法·leetcode·职场和发展
qyzm2 小时前
Educational Codeforces Round 189 (Rated for Div. 2)
数据结构·python·算法
fox_lht2 小时前
8.3.使用if let和let else实现简明的程序流控制
开发语言·后端·算法·rust
磊 子2 小时前
类模板与派生1
java·开发语言·c++