题解:计算约数个数

题目理解

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

输入格式

  • 第一行:正整数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而非浮点数比较

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

相关推荐
minji...2 分钟前
Linux高级IO(六)基于ET模式、单reactor反应堆的epoll版本的TCP计算服务器
linux·服务器·网络·c++·epoll·socket套接字·reactor反应堆模式
程序大视界2 分钟前
【C++ 从基础到项目实战】C++(九):友元与设计模式初探——打破封装的艺术
开发语言·c++·cpp
東隅已逝,桑榆非晚5 分钟前
C语言预处理详解:从宏到条件编译
c语言·笔记·算法
cpp_25018 分钟前
P10377 [GESP202403 六级] 好斗的牛
数据结构·c++·算法·题解·洛谷·gesp六级
邪修king9 分钟前
C++ 红黑树自平衡核心:旋转变色、规则详解与 STL 选型逻辑
数据结构·c++·b树·算法
随意起个昵称2 小时前
线性dp-计数类题目10(ZBRKA)
算法·动态规划
Navigator_Z8 小时前
LeetCode //C - 1089. Duplicate Zeros
c语言·算法·leetcode
cany10008 小时前
C++ -- 可变参数模板
c++
不会C语言的男孩9 小时前
C++ Primer 第2章:变量和基本类型
开发语言·c++
云泽80810 小时前
C++ 可调用对象通关指南:深度解析 Lambda 表达式、function 包装器与 bind 绑定器
开发语言·c++·算法