算法学习笔记(34)——约数

约数

约数的定义

若整数 n 除以整数 d 的余数为 0,即 d 能整除 n,则称 dn 的约数,nd 的倍数,记为 d|n

算数基本定理的推论

正约数集合

在算数基本定理中,若正整数 N 被唯一分解为 N=p1c1p2c2...pmcm,其中 ci 都是正整数,pi 都是质数,且满足 p1<p2<...<pm,则 N 的正约数集合可以写作:

{p1c1p2c2...pmcm},0bici

正约数个数

(c1+1)(c2+1)...(cm+1)=i=1m(ci+1)

此处 1 代表质数 pi0 次方。

正约数之和

(1+p1+p12+...+p1c1)...(1+pm+pm2+...+pmcm)=i=1m(j=0ci(pi)j)

上式按照乘法多项式展开之后的每一项都是 N 的一个约数。

一、试除法求约数

如果 d>NN 的约数,那么 N/dN 也是 N 的约数。换言之,约数总是成对出现的(对于完全平方的数字,N 会单独出现一次)。

因此,只需扫描 1N 之间的数字,尝试能否整除 N,若能整除,则 N/d 也是 N 的约数。

时间复杂度:O(N)

题目链接:AcWing 869. 试除法求约数

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void get_divisors(int n)
{
    // 用于存储约数
    vector<int> res;
    
    // 枚举从 1 到 sqrt(n) 的数
    for (int i = 1; i <= n / i; i ++ )
        // 如果 i 是 n 的约数
        if (n % i == 0) {
            // 将 i 存起来
            res.push_back(i);
            // 如果不是完全平方数,则将 n/i 也存起来
            if (i != n / i) res.push_back(n / i);
        }
    
    // 排序约数数组
    sort(res.begin(), res.end());
    
    for (auto i : res) cout << i << ' ';
    puts("");
}

int main()
{
    int n;
    cin >> n;
    while (n -- ) {
        int a;
        cin >> a;
        get_divisors(a);
    }
    return 0;
}

二、约数个数

利用算数基本定理的推论

题目链接:AcWing 870. 约数个数

时间复杂度:O(N)

#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long LL;

const int MOD = 1e9 + 7;

int main()
{
    int n;
    cin >> n;
    
    // 采用哈希表存储每一个质因子对应出现的次数
    unordered_map<int, int> primes;
    while (n -- ) {
        int a;
        cin >> a;
        
        // 筛质数
        for (int i = 2; i <= a / i; i ++ )
            while (a % i == 0) {
                a /= i;
                primes[i] ++;
            }
        if (a > 1) primes[a] ++;
    }
    
    LL res = 1;
    // 代公式
    for (auto prime : primes) res = res * (prime.second + 1) % MOD;
    cout << res << endl;
    
    return 0;
}

三、约数之和

题目链接:AcWing 871. 约数之和

时间复杂度:O(N)

#include <iostream>
#include <unordered_map>

using namespace std;

const int MOD = 1e9 + 7;

typedef long long LL;

int main()
{
    int n;
    cin >> n;
    
    unordered_map<int, int> primes;
    while (n -- ) {
        int a;
        cin >> a;
        
        for (int i = 2; i <= a / i; i ++ )
            while (a % i == 0) {
                a /= i;
                primes[i] ++;
            }
        if (a > 1) primes[a] ++;
    }
    
    LL res = 1;
    for (auto prime : primes) {
        int p = prime.first, c = prime.second;
        LL t = 1;
        while (c -- ) t = (t * p + 1) % MOD;
        res = res * t % MOD;
    }
    cout << res << endl;
    
    return 0;
}

四、最大公约数

欧几里得算法

a,bN,b0,gcd(a,b)=gcd(b,amodb)

证明:

  • ab,则 gcd(b,amodb)=gcd(b,a)=gcd(a,b),显然成立。
  • ab,不妨设 a=qb+r,其中 0r<b。显然 r=amodb。对于 a,b 的任意公约数 d,因为 d|a, d|qb,故 d|(aqb) ,即 d|r,因此 d 也是 b,r 的公约数。反之亦成立。故 a,b 的公约数集合与 b,amodb 的公约数集合相同。于是他们的最大公约数自然相等。

时间复杂度:O(log(a+b))

题目链接:AcWing 872. 最大公约数

#include <iostream>

using namespace std;

int gcd(int a, int b)
{
    // 0可以被任何数整除
    return b ? gcd(b, a % b) : a;
}

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

也可调用STL的__gcd()函数:

#include <iostream>
#include <algorithm>

using namespace std;

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

欧几里得算法是最常用的求最大公约数的算法。不过,因为高精度除法(取模)不容易实现,需要做高精度运算时,可考虑用更相减损数代替欧几里得算法。

更相减损数

a,bN,ab,gcd(a,b)=gcd(b,ab)=gcd(a,ab)a,bN,gcd(2a,2b)=2gcd(a,b)

posted @   S!no  阅读(277)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示