🐋
Blog
算法
算法
  • 算法
  • 基础
    • 高精度
    • 二分
    • 位运算
    • 贪心
    • KMP
    • Master Theorem
    • 前缀和 & 差分
    • sort
    • 双指
  • 数据结构
    • 数据结构模拟
  • 数学
    • 组合数
    • 约数
    • 欧拉函数
    • 扩展欧几里得
    • 高斯消元
    • 容斥原理
    • 线性筛
    • 快速幂
  • 动态规划
    • 背包
    • 字符串匹配
    • 区间DP
  • 图论
    • BFS
    • DFS
由 GitBook 提供支持
在本页
  1. 数学

约数

约数就是因数。主要记录一下约数个数、约数之和以及最大公约数的计算方法。

如何求约数

  • 试除法是一个很好的办法。这个算法的大致意思是让小于 n 的数都和 n 相除,如果余数为零可以判断为合数。

  • 有了大致思路还不行,还可以优化。合数都是成对出现的,对吧。我们用 n 除以 i,余数为零,那么商也是另一个约数。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

int main(){
    int n; cin >> n;
    while (n -- ){
        int m; cin >> m;
        vector<int> p;
        for (int i = 1; i <= m / i; i ++){
            if (m % i == 0){
                p.push_back(i);
                if (m / i != i) p.push_back(m / i);
            }
        }
        sort(p.begin(), p.end());
        for (int i = 0; i < p.size(); i ++)
            cout << p[i] << ' ';
        cout << endl;
    }
}

时间复杂度分析

约数个数

给定一个正整数怎么求约数的个数,此时需要一个定理,伟大的欧几里得数学家曾发现的。

算术基本定理

原理分析

#include<iostream>
#include<algorithm>
#include<unordered_map>

using namespace std;
const int N = 1e9 + 7;

int main(){
    int n; cin >> n;
    long long res = 1;
    unordered_map<int, int> hs;
    while (n -- ){
        int m; cin >> m;
        for (int i = 2; i <= m / i; i ++){ //枚举到 i*i <= m
            if (m % i == 0){
                while (m % i == 0){
                    hs[i] ++;
                    m /= i;
                }
            } 
        }
        if (m > 1) hs[m]++; // 只存在一个大于根号m的数是m的分解质数
    }
    for (auto prime : hs) res = res * (prime.second + 1) % N;
    cout << res;
}

时间复杂度分析

约数之和

给定一个正整数,求他的约数的和。

原理分析

#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int N = 1e9 + 7;
long long horner(int p, int x){
    long long res = 1;
    for (int i = 0 ; i < x; i ++){
        res = (res * p + 1) % N;
    }
    return res;
}

int main(){
    int n ; cin >> n;
    unordered_map<int, int> primes;
    while (n -- ){
        int m ; cin >> m;
        for (int i = 2; i <= m / i; i ++){
            if (m % i == 0){
                while (m % i == 0){
                    primes[i] ++;
                    m /= i;
                }
            }
        }
        if (m > 1) primes[m] ++;
    }
    long long res = 1;
    for (auto prime : primes){
        res = res * horner(prime.first, prime.second) % N; 
    }
    cout << res;
}

时间复杂度分析

最大公约数

最大公约数就比较简单了,小学都学过,辗转相除法计算,就不多说了。

#include<iostream>
#include<algorithm>
using namespace std;

int gcd(int a ,int b){
    return a == 0 ? b : gcd( b % a, a);
}

int main(){
    int n; cin >> n;
    while (n -- ){
        int a, b; cin >> a >> b;
        cout << gcd(a, b) << endl;
    }
}

时间复杂度分析

总结

约数这一小结不难,理解算术基本定理如何解决问题的就行了。

上一页组合数下一页欧拉函数

最后更新于2年前

最外层时间为 nnn,内层循环是 m\sqrt {m}m​。还有一个排序 nlog⁡n n \log n nlogn,但是这个排序的时间是远小于 m\sqrt {m}m​。为什么那?排序的 nnn 也就是每个数的合数的个数,从 1∼n1 \sim n1∼n 大概有 nln⁡nn\ln nnlnn 个合数,平均到每个数大概有 ln⁡n\ln nlnn 个合数(我们要计算的是个数),那么排序的实现复杂度为 ln⁡nlog⁡ln⁡n\ln n \log {\ln n}lnnloglnn,小于 m\sqrt {m}m​。所以总的时间复杂度为 O(nm)O(n\sqrt m)O(nm​)。

任何一个正整数且不是质数的数 NNN 都可以分解为 N=p1a1∗p2a2∗⋯∗pnanN = p_1^{a_1} * p_2^{a_2} * \cdots * p_n^{a_n}N=p1a1​​∗p2a2​​∗⋯∗pnan​​ ,其中任意一个 ppp 都是质数且 p1<p2<⋯<pnp_1 \lt p_2 \lt \cdots \lt p_np1​<p2​<⋯<pn​ 。这是 NNN 的标准分解式。

任何一个 NNN 可以分解为 p1a1∗p2a2∗⋯∗pnanp_1^{a_1} * p_2^{a_2} * \cdots * p_n^{a_n}p1a1​​∗p2a2​​∗⋯∗pnan​​,而任何一个 NNN 的合数都可以表示为p1b1∗p2b2∗⋯∗pnbnp_1^{b_1} * p_2^{b_2} * \cdots * p_n^{b_n}p1b1​​∗p2b2​​∗⋯∗pnbn​​,其中 0<=bi<=ai0 <= b_i <= a_i0<=bi​<=ai​。那么 NNN 的合数的个数不就是一个组合数 ∏i=1n(1+ai)\prod_{i=1}^{n}(1+a_i)∏i=1n​(1+ai​),我们按照算术基本定理计算就可。

最外层时间 nnn,里层 for 循环时间为 m\sqrt mm​, 最里层 while 循环均摊到 for 循环的时间为 111,所以总的时间复杂度 O(n∗m)O(n * \sqrt m)O(n∗m​)。

由算术基本定理可得,一个正整数可以分解为 N=p1a1∗p2a2∗⋯∗pnanN = p_1^{a_1} * p_2^{a_2} * \cdots * p_n^{a_n}N=p1a1​​∗p2a2​​∗⋯∗pnan​​,而任何一个 NNN 的合数都可以表示为 p1b1∗p2b2∗⋯∗pnbnp_1^{b_1} * p_2^{b_2} * \cdots * p_n^{b_n}p1b1​​∗p2b2​​∗⋯∗pnbn​​,其中 0<=bi<=ai0 <= b_i <= a_i0<=bi​<=ai​。那么约数之和可以表示为 (p11+p12+⋯+p1a1)∗⋯∗(pn1+pn2+⋯+pnan)(p_1^1 + p_1^2 + \cdots + p_1^{a_1}) * \cdots * (p_n^1 + p_n^2 + \cdots + p_n^{a_n})(p11​+p12​+⋯+p1a1​​)∗⋯∗(pn1​+pn2​+⋯+pnan​​),可以简洁的表示为 ∏i=1n∑j=1aipij\prod_{i = 1}^n {\sum_{j = 1}^{a_i} p_i^j}∏i=1n​∑j=1ai​​pij​

值得注意的是,在计算单独的质因数的合数时,也就是horner函数使用的是秦九韶算法,并非等比数列求和。原因在于可以会溢出,使用秦九韶算法可以每次计算时mod一次,不会溢出。此处补充一个代数系统下的公式 (a∗b)modN=(a modN∗b modN)modN(a * b) mod N = (a \ mod N * b \ mod N) mod N(a∗b)modN=(a modN∗b modN)modN。

分解质因数的时间复杂度同约数个数为 O(nm)O(n \sqrt m)O(nm​),至于求解之和的过程,比较难计算,在此先忽略了。(哈哈)

递归函数的时间复杂度,一般用 master theorem 来分析,可讲的就多了,新开另一个来说。但这里又引入了 mod 运算,主定理也解决不了,可就难的多了。这里贴一个大佬的。

分析