数学知识(一)——数论(质数、约数)

本文介绍了数论的基础知识,包括质数的试除法判定、分解质因数、筛法(朴素筛与线性筛),以及约数的试除法求解、个数和之和的计算,最后讲解了欧几里得算法求最大公约数。文章强调理解原理的重要性,并提供了AcWing平台的相关模板题作为实践练习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

算法基础课-4.1

第四章 数学知识(一)
数论(质数、约数)
共7题,知识点如下:

质数:
试除法判定质数、
试除法分解质因数、
筛质数—埃氏筛法,线性筛法。

约数:
试除法求约数、
约数个数、
约数之和、
最大公约数—欧几里得算法。

质数约数都属于数论

数论这章一定要把原理理解了,否则很难把题做出来

质数

试除法判定质数

时间复杂度一定是根号n

在这里插入图片描述

模板

bool is_prime(int x)
{
    if (x < 2) return false;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
            return false;
    return true;
}

模板题 AcWing 866. 试除法判定质数

#include <iostream>

using namespace  std;

bool is_prime(int n) {
    if (n < 2) return false;
    // 推荐使用i <= n / i
    // 原因:使用i <= sqrt(n) 每次会调用函数,速度很慢
    //       使用i * i <= n,当n接近int最大值时,i*i会爆int
    for (int i = 2; i <= n / i; i++) {
        if (n % i == 0) return false;
    }
    return true;
}

int main() {
    int n;
    cin >> n;
    while (n --) {
        int x;
        cin >> x;
        if (is_prime(x)) puts("Yes");
        else puts("No");
    }
    return 0;
}

试除法分解质因数

质因数: 在数论里是指能整除给定正整数的质数。

每个合数都可以写成几个质数相乘的形式,如 6=2*3, 8=23 ,

这几个质数就叫做这个合数的质因数

时间复杂度可以比根号n小

比如 n 为 2k ,进循环就除干净了 n=1,结束了,此时时间复杂度为 logn

在这里插入图片描述
如果有两个,乘一块就比n大了

模板

void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}

模板题 AcWing 867. 分解质因数

#include <iostream>

using namespace  std;

// 输出每个质因数的底数和指数。
void divide(int n) {
    // 枚举所有于根号n的质因子
    for (int i = 2; i <= n / i; i++) {
        if (n % i == 0) { // i一定是质数
            // 统计次数(指数)
            int s = 0;
            while(n % i == 0) {
                n /= i;
                s ++;
            }
            printf("%d %d\n", i, s);
        }
    }
    // 处理剩下的,大于根号n的质因子
    if (n > 1) printf("%d %d\n", n, 1);
    puts("");
}

int main() {
    int n;
    cin >> n;
    while (n --) {
        int x;
        cin >> x;
        divide(x);
    }
    return 0;
}

筛质数

在这里插入图片描述

从前往后,将每个数后面所有是他倍数的数删去

如果p最后留下来了,说明1-p-1不存在任何p的约数

模板—朴素筛法求素数

int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

模板—线性筛法求素数

int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

模板题 AcWing 868. 筛质数

埃氏筛法

埃氏筛法
其实并不需要将2-p枚举一遍,只判断一下当中的质数即可

2-p-1中的质数只要不是p的约数,p就是一个质因数

不是质数的时候就不需要筛他的所有数的倍数
在这里插入图片描述
在这里插入图片描述

当n为232时,loglogn 才约等于5,所以是接近 O(n) 的

#include <iostream>

using namespace  std;

const int N = 1e6 + 10;

int primes[N], cnt;
bool st[N];

// 求出 1∼n 中质数的个数。
void get_primes(int n) {
    for (int i= 2; i <= n; i++) {
        if (!st[i]) { // 没被筛过,说明是质数
            primes[cnt ++] = n;
            // 删掉每个数的倍数
            for (int j = i + i; j <= n; j += i) st[j] = true;
        }
    }
}

int main() {
    int n;
    cin >> n;

    get_primes(n);
    
    cout << cnt << endl;
    return 0;
}

线性筛法

核心原理:保证每个数一定是被它的最小质因子筛掉

数据范围107 时候,速度比埃氏筛法快一倍左右

106 的时候,速度差不多

在这里插入图片描述

任何一个合数(非质数)都会被筛掉,而且只会用最小质因子来筛

而每个数都只有一个最小质因子

所以每个数只会被筛一次,所以是线性的

如果i是合数,碰到 primes[j](最小质因子) ,就一定会停下来

i是质数时,碰到 primes[j] = i 的时候 ,也会停下来

#include <iostream>

using namespace  std;

const int N = 1e6 + 10;

int primes[N], cnt;
bool st[N];

// 求出 1∼n 中质数的个数。

void get_primes(int n) {
    for (int i= 2; i <= n; i++) {
        if (!st[i]) primes[cnt ++] = i;
        // 从小到大枚举所有质数
        for (int j = 0; primes[j] <= n / i; j++) {
            // 把当前质数和i的乘积筛掉
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break; // primes[j]一定是i的最小质因子(从小到大枚举的)

        }
    }
}

int main() {
    int n;
    cin >> n;

    get_primes(n);
    
    cout << cnt << endl;
    return 0;
}

不用纠结为啥这么筛,只需要知道

目的是比传统枚举到根号n速度要快即可

质数倍数是非质数

埃氏筛法:用0-n-1间的质数来排除没用的数(非质数一定可以整除某个质数)

线性筛法:用最小质因数排除(2357等)

一般都用线性筛法,但埃氏筛法的思路比较重要,可以用来解决其他问题

约数

算术基本定理

百度
在这里插入图片描述

维基
在这里插入图片描述
在这里插入图片描述

约数

约数也一定是成对出现的,枚举较小的即可

只需要枚举到根号n即可

可以通过每个数有多少个倍数,来求有多少约数

在这里插入图片描述

int 范围内,约数最大的,大约是1500个

试除法求所有约数

时间复杂度根号n

模板

vector<int> get_divisors(int x)
{
    vector<int> res;
    for (int i = 1; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}

模板题 AcWing 869. 试除法求约数

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

using namespace std;

// 从小到大的顺序输出它的所有约数
vector<int> get_divisors(int n) {
    vector<int> res;
    
    for (int i = 1; i <= n / i; i++) {
        if (n % i == 0) { // i是n的约数
            res.push_back(i);
            // 防止加入重复的(比如9,是3的平方)
            if (i != n / i) res.push_back(n / i);
        }
    }
    sort(res.begin(), res.end());
    return res;
}

int main() {
    int n;
    cin >> n;
    
    while (n --) {
        int x;
        cin >> x;
        auto res = get_divisors(x);
        for (auto t : res) cout << t << " ";
        cout << endl;
    }
    return 0;
}

约数个数和约数之和

模板

如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

模板题 AcWing 870. 约数个数,

在这里插入图片描述

分解方法:分别分解每一个

比如分解a1,将a1所有质因子找出来,然后累加次数即可

在这里插入图片描述

#include <iostream>
#include <algorithm>
#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 x;
        cin >> x; // 对每一个a[i]
        // 累加所有质因子的出现次数
        for (int i= 2; i <= x / i; i++) {
            while (x % i == 0) {
                x /= i;
                primes[i] ++; // 存了所有质因数的指数
            }
        }
        // 处理大于根号n的质因数
        if (x > 1) primes[x] ++;
        
    }
    // 把所有指数+1相乘即可
    LL  res = 1;
    for (auto prime : primes) res = res * (prime.second + 1) % mod;
    
    cout << res << endl;
    
    return 0;
}

模板题 AcWing 871. 约数之和

两个公式是独立的

在这里插入图片描述
用乘法分配律展开,就是所有约数加到一块
在这里插入图片描述

#include <iostream>
#include <algorithm>
#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 x;
        cin >> x; // 对每一个a[i]
        // 累加所有质因子的出现次数
        for (int i= 2; i <= x / i; i++) {
            while (x % i == 0) {
                x /= i;
                primes[i] ++; // 存了所有质因数的指数
            }
        }
        // 处理大于根号n的质因数
        if (x > 1) primes[x] ++;
        
    }
    // 直接带入求和的公式即可
    LL  res = 1; // 每一个质因数相乘即可
    for (auto prime : primes)  {
        // p:质数的底数,a:质数的指数
        int p = prime.first, a = prime.second;
        LL t = 1; 
        // 第一次:【p+1】-->第二次:【p^2+p+1】--->第a次 【p^a +p^(a-1)+···+p+1】
        while (a --) t = (t * p + 1) % mod;
        res = res * t % mod;
    }
    
    cout << res << endl;
    
    return 0;
}

欧几里得算法

也叫辗转相除法

整除
在这里插入图片描述
a整除b写作a|b ,其实是 b ÷ a 余数为0

其中a是除数,b是a的倍数(被除数)
在这里插入图片描述
a与b 的公约数,就等于 b 和 a模b 的公约数
在这里插入图片描述
将a//b看成c
即可以将a mod b 转成 a - c*b
在这里插入图片描述
而d能整除b,所有可以把右边的约掉,只需要求d能整除a即可
在这里插入图片描述
所有左右两边的公约数是相等的
在这里插入图片描述

很常见,一定要记住该模板

欧几里得算法时间复杂度是 logn

模板

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

模板题 AcWing 872. 最大公约数

#include <iostream>

using namespace std;

int gcb(int a, int b) {
    // b如果不是0,返回b和a模b,是0就返回a,因为可以整除任何数
    return b ? gcb(b, a % b) : a;
}

int main() {
    int n;
    scanf("%d", &n);
    while (n --) {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%d\n", gcb(a, b));
    }
    return 0;
}

整除和除的区别
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值