算法基础复盘笔记Day08【数学知识】—— 质数、约数、快速幂

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

第一章 质数

一、试除法判定质数

1. 题目描述

给定 n 个正整数 a i a_i ai,判定每个数是否是质数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个正整数 a i a_i ai

输出格式

共 n 行,其中第 i 行输出第 i 个正整数 a i a_i ai 是否为质数,是则输出 Yes,否则输出 No

数据范围

1 ≤ n ≤ 100 1≤n≤100 1n100,
1 ≤ a i ≤ 2 31 − 1 1≤a_i≤2^{31}−1 1ai2311

输入样例:

2
2
6

输出样例:

Yes
No

2. 思路分析

质数定义: 一个大于1的自然数,除了1和它本身外,不能被其他自然数整除,换句话说就是该数除了1和它本身以外不再有其他的因数,这个数就是质数。

给定一个数 x x x,判断 x x x 是否为质数:
x x x 除以 [ 2 , x − 1 ] [2 ,x - 1] [2,x1] 中的每个数,如果出现了余数为 0, 则这个数不是质数,如果没有出现余数为 0,则这个数是质数。

优化:
一个数 x x x 分解成两个数的乘积,则这两个数中,一定有一个数大于 x \sqrt{x} x ,一个数小于 x \sqrt{x} x
所以,可以用 x x x 除以 [2, x \sqrt{x} x ] 中的每个数,如果出现了余数为 0,则这个数不是质数,如果没有出现余数为 0,则这个数是质数。


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

bool is_prime(int x)
{
    if (x < 2) return false; //2是最小的质数,如果n小于2,那n肯定就不是质数
    for (int i = 2; i <= x / i; i ++) //不要用开方函数或者i*i小于x。开方函数慢,i*i可能越界
        if (x % 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;
}

二、分解质因数

1. 题目描述

给定 n 个正整数 a i a_i ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个正整数 a i a_i ai

输出格式

对于每个正整数 a i a_i ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。

每个正整数的质因数全部输出完毕后,输出一个空行。

数据范围

1 ≤ n ≤ 100 1≤n≤100 1n100,
2 ≤ a i ≤ 2 × 1 0 9 2≤a_i≤2×10^9 2ai2×109

输入样例:

2
6
8

输出样例:

2 1
3 1

2 3

2. 思路分析

  • x x x 的质因子最多只包含一个大于 x \sqrt{x} x 的质数。如果有两个,这两个因子的乘积就会大于 x x x,矛盾。
  • i i i 从 2 遍历到 x \sqrt{x} x 。 用 x / i x / i x/i,如果余数为 0,则 i i i 是一个质因子。
  • s s s 表示质因子 i i i 的指数, x / = i x /= i x/=i 为 0,则 s + + s++ s++ x = x / i x = x / i x=x/i
  • 最后检查是否有大于 x \sqrt{x} x 的质因子,如果有,输出。

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

void divide(int x)
{
    for (int i = 2; i <= x / i; i ++) //i <= x / i:防止越界,速度大于 i < sqrt(x)
        if (x % i == 0) //i为底数
        {
            int s = 0; //s为指数
            while (x % i == 0) x /= i, s ++;
            cout << i << ' ' << s << endl; //输出底数和指数
        } 
    if (x > 1) cout << x << ' ' << "1" << endl; //如果x还有剩余,单独处理
    cout << endl;
}

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

三、筛质数

1.题目描述

给定一个正整数 n,请你求出 1 ∼ n 1∼n 1n 中质数的个数。

输入格式

共一行,包含整数 n。

输出格式

共一行,包含一个整数,表示 1 ∼ n 1∼n 1n 中质数的个数。

数据范围

1 ≤ n ≤ 1 0 6 1≤n≤10^6 1n106

输入样例:

8

输出样例:

4

2. 思路分析

线性筛法(又称欧拉筛法)

从小到大每个每个数:

  1. 如果当前数没被划掉,必定是质数,将该质数加入数组;
  2. 枚举已记录的质数(如果合数已越界则中断)
    (1)合数未越界,则划掉合数;
    (2)条件 i % p == 0,保证合数只被最小质因子划掉
    • i i i 是质数,则最多枚举到自身中断;
    • i i i 是合数,则最多枚举到自身的最小质数中断。

在这里插入图片描述


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

//primes数组用来存放质数,cnt保存质数的个数
int primes[N], cnt;
bool st[N]; //st[i],i为质数则为false,否则为true

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 ++)
        {
            //标记,primes[j]一定是primes[j]*i的最小质因子
            st[primes[j] * i] = true;
            //表明primes[j]一定是i的最小质因子,没有必要再遍历,primes要小于等于i的最小质因子
            //这样能保证每个数遍历一遍,而没有重复
            //每个数只被它的最小质因数消除
            if (i % primes[j] == 0) break;
        }
    }
}

int main()
{
    int n;
    cin >> n;
    
    get_primes(n);
    cout << cnt << endl;
    
    return 0;
}

第二章 约数

一、试除法求约数

1. 题目描述

给定 n 个正整数 a i a_i ai,对于每个整数 a i a_i ai,请你按照从小到大的顺序输出它的所有约数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数 a i a_i ai

输出格式

输出共 n 行,其中第 i 行输出第 i 个整数 a i a_i ai 的所有约数。

数据范围

1 ≤ n ≤ 100 1≤n≤100 1n100,
2 ≤ a i ≤ 2 × 1 0 9 2≤ a_i≤2×10^9 2ai2×109

输入样例:

2
6
8

输出样例:

1 2 3 6 
1 2 4 8 

2. 思路分析

与试除法判定质数一样,约数都是成对出现,故只需判定约数中的较小数即可。

因此,只需要用 x x x 除以 1 到 根号x 之间的数,如果余数是0,则把 除数 以及 x / 除数 加到答案中。


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

vector<int> get_divisor(int x)
{
    vector<int> res;
    //因为约数成对出现,所以只需要循环到根号x
    for (int i = 1; i <= x / i; i ++)
        if (x % i == 0)
        {
            res.push_back(i);
            //如果n不是i的平方,那n / i就是一个新的约数,把n / i压入vector
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}

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

二、约数个数

1. 题目描述

给定 n 个正整数 a i a_i ai,请你输出这些数的乘积的约数个数,答案对 1 0 9 + 7 10^9+7 109+7 取模。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数 a i a_i ai

输出格式

输出一个整数,表示所给正整数的乘积的约数个数,答案需对 1 0 9 + 7 10^9+7 109+7 取模。

数据范围

1 ≤ n ≤ 100 1≤n≤100 1n100,
1 ≤ a i ≤ 2 × 1 0 9 1≤a_i≤2×10^9 1ai2×109

输入样例:

3
2
6
8

输出样例:

12

2. 思路分析

N = ∏ i = 1 k p i a i = p 1 a 1 ⋅ p 2 a 2 ⋅ ⋅ ⋅ p k a k N = \displaystyle \prod_{i=1}^{k}{p_i^{a_i}} = p_1^{a_1} · p_2^{a_2} ···p_k^{a_k} N=i=1kpiai=p1a1p2a2⋅⋅⋅pkak

约数个数: ∏ i = 1 k ( a i + 1 ) = ( a 1 + 1 ) ( a 2 + 1 ) ⋅ ⋅ ⋅ ( a k + 1 ) \displaystyle \prod_{i=1}^{k}{(a_i+1)} = (a_1+1)(a_2+1)···(a_k+1) i=1k(ai+1)=(a1+1)(a2+1)⋅⋅⋅(ak+1)

约数之和: ∏ i = 1 k ∑ j = 0 a i p i j = \displaystyle \prod_{i=1}^{k}{\displaystyle \sum_{j=0}^{a_i}p_i^j} = i=1kj=0aipij= ∏ i = 1 k ( p i 0 + p i 1 + . . . + p i a i ) \displaystyle \prod_{i=1}^{k}{(p_i^0+p_i^1+...+p_i^{a_i})} i=1k(pi0+pi1+...+piai) = ( p 1 0 + p 1 1 + . . . . + p 1 a 1 ) ( p 2 0 + p 3 1 + . . . . + p 2 a 2 ) ⋅ ⋅ ⋅ ( p k 0 + p k 1 + . . . . + p k a k ) (p_1^0 + p_1^1 + ....+p_1^{a_1})(p_2^0 + p_3^1 + ....+p_2^{a_2})···(p_k^0 + p_k^1 + ....+p_k^{a_k}) (p10+p11+....+p1a1)(p20+p31+....+p2a2)⋅⋅⋅(pk0+pk1+....+pkak)
 

思路就是先把原数分解为质因数,最后把每一个数的指数累加即可。从a1一直分解到an,由于a的数据过大,此处用哈希表进行存储。


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 110, mod = 1e9 + 7;

int main()
{
    int n;
    cin >> n;
    
    //保存底数和指数
    unordered_map<int, int> primes;
    
    while (n --)
    {
        int x;
        cin >> x;
        for (int i = 2; i <= x / i; i ++)
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++; //指数+1
            }
         //如果有剩余,也是一个质因子
        if (x > 1) primes[x] ++;
    }
    
    LL res = 1;
    //res = (x^1 + 1)(x^2 + 1)(x^3 + 1)…(x^k + 1)
    for (auto p : primes) res = res * (p.second + 1) % mod;
    cout << res << endl;
    
    return 0;
}

三、约数之和

1.题目描述

给定 n 个正整数 a i a_i ai,请你输出这些数的乘积的约数之和,答案对 1 0 9 + 7 10^9+7 109+7 取模。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数 a i a_i ai

输出格式

输出一个整数,表示所给正整数的乘积的约数之和,答案需对 1 0 9 + 7 10^9+7 109+7 取模。

数据范围

$1≤n≤100,
1 ≤ a i ≤ 2 × 1 0 9 1≤a_i≤2×10^9 1ai2×109

输入样例:

3
2
6
8

输出样例:

252

2. 思路分析

N = ∏ i = 1 k p i a i = p 1 a 1 ⋅ p 2 a 2 ⋅ ⋅ ⋅ p k a k N = \displaystyle \prod_{i=1}^{k}{p_i^{a_i}} = p_1^{a_1} · p_2^{a_2} ···p_k^{a_k} N=i=1kpiai=p1a1p2a2⋅⋅⋅pkak

约数个数: ∏ i = 1 k ( a i + 1 ) = ( a 1 + 1 ) ( a 2 + 1 ) ⋅ ⋅ ⋅ ( a k + 1 ) \displaystyle \prod_{i=1}^{k}{(a_i+1)} = (a_1+1)(a_2+1)···(a_k+1) i=1k(ai+1)=(a1+1)(a2+1)⋅⋅⋅(ak+1)

约数之和: ∏ i = 1 k ∑ j = 0 a i p i j = \displaystyle \prod_{i=1}^{k}{\displaystyle \sum_{j=0}^{a_i}p_i^j} = i=1kj=0aipij= ∏ i = 1 k ( p i 0 + p i 1 + . . . + p i a i ) \displaystyle \prod_{i=1}^{k}{(p_i^0+p_i^1+...+p_i^{a_i})} i=1k(pi0+pi1+...+piai) = ( p 1 0 + p 1 1 + . . . . + p 1 a 1 ) ( p 2 0 + p 3 1 + . . . . + p 2 a 2 ) ⋅ ⋅ ⋅ ( p k 0 + p k 1 + . . . . + p k a k ) (p_1^0 + p_1^1 + ....+p_1^{a_1})(p_2^0 + p_3^1 + ....+p_2^{a_2})···(p_k^0 + p_k^1 + ....+p_k^{a_k}) (p10+p11+....+p1a1)(p20+p31+....+p2a2)⋅⋅⋅(pk0+pk1+....+pkak)
 

  1. 在每次输入一个数时,分解其质因数,将其出现的次数保存起来;
  2. 遍历保存质因数的表,将每个质因数带入公式中。

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 110, mod = 1e9 + 7;

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

四、最大公约数

1. 题目描述

给定 n 对正整数 a i , b i a_i,b_i ai,bi,请你求出每对数的最大公约数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数对 a i , b i a_i,b_i ai,bi

输出格式

输出共 n 行,每行输出一个整数对的最大公约数。

数据范围

1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105,
1 ≤ a i , b i ≤ 2 × 1 0 9 1≤a_i,b_i≤2×10^9 1ai,bi2×109

输入样例:

2
3 6
4 6

输出样例:

3
2

2. 思路分析

欧几里得算法(辗转相除法)

( a , b ) (a, b) (a,b) 和 (b, a%b) 的公约数是相同的,故最大公约数也相同,即 gcd(a, b) = gcd(b, a % b)

递归求解:直到 a % b == 0,此时的 b b b 就是最大公约数。

模拟:求28和16的最大公约数
gcd(28,16)
gcd(16,12)
gcd(4,0)
return 4

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

int gcd(int a, int b)
{
    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;
}

第三章 快速幂

一、快速幂

1. 题目描述

给定 n 组 a i , b i , p i a_i,b_i,p_i ai,bi,pi,对于每组数据,求出 a i b i m o d p i a_i^{b_i} mod p_i aibimodpi 的值。

输入格式

第一行包含整数 n。

接下来 nn 行,每行包含三个整数 a i , b i , p i a_i,b_i,p_i ai,bi,pi

输出格式

对于每组数据,输出一个结果,表示 a i b i m o d p i a_i^{b_i} mod p_i aibimodpi 的值。

每个结果占一行。

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000,
1 ≤ a i , b i , p i ≤ 2 × 1 0 9 1≤a_i,b_i,p_i≤2×10^9 1ai,bi,pi2×109

输入样例:

2
3 2 5
4 3 9

2. 思路分析

a n = a × a × ⋅ ⋅ ⋅ × a a^n = a×a×···×a an=a×a×⋅⋅⋅×a,暴力的计算需要 O ( n ) O(n) O(n)的时间。
快速幂使用二进制拆分倍增思想,仅需要 O ( l o g n ) O(logn) O(logn) 的时间。

n n n 做二进制拆分,例如: 3 13 = 3 ( 1101 ) 2 = 3 8 ⋅ 3 4 ⋅ 3 1 3^{13} = 3^{(1101)_2} = 3^8 · 3^4 ·3^1 313=3(1101)2=383431
a a a 做平方倍增,例如: 3 1 , 3 2 , 3 4 , 3 8 , . . . . . 3^1,3^2,3^4,3^8,..... 31,32,34,38,.....

n n n l o g n + 1 logn + 1 logn+1 个二进制,我知道了 3 1 , 3 2 , 3 4 , 3 8 , . . . . . , a 2 l o g n 3^1,3^2,3^4,3^8,.....,a^{2^{logn}} 31,32,34,38,.....,a2logn后,只需要计算 l o g n + 1 logn + 1 logn+1 次乘法就可以了。


模拟求出 3 13 3^{13} 313:

  1. (1101) & 1 = 1; res = 1 * 3 = 3; a = 3 * 3 = 3 2 3^2 32; n = (110);
  2. (110) & 1 = 0; a = 3 2 3^2 32 * 3 2 3^2 32 = 3 4 3^4 34; n = (11);
  3. (11) & 1 = 1; res = 3 * 3 4 3^4 34 ; a = 3 4 3^4 34 * 3 4 3^4 34 = 3 8 3^8 38; n = (1);
  4. (1) & 1 = 1; res = 3 * 3 4 3^4 34 * 3 8 3^8 38 ; a = 3 8 3^8 38 * 3 8 3^8 38 = 3 16 3^{16} 316; n = (0);
  5. 返回 r e s res res

快速幂可以应用在任何具有结合律的运算中,例如:取模运算、矩阵乘法等。
例如: ( 3 13 ) (3^{13}) (313) % p p p = ( ( 3 8 ) (3^8) (38) % p p p · ( 3 4 ) (3^4) (34) % p p p · ( 3 1 ) (3^1) (31) % p p p) % p p p


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p)
{
    LL res = 1 % p;
    // 求k的二进制数,再用这二进制数求出a的k次方模p的结果
    while (b)
    {
        // 如果当前k的末尾是1
        if (b & 1) res = res * a % p;
        // 置a的值为a的平方模p
        a = a * (LL)a % p;
        //向右移一位
        b >>= 1;
    }
    return res;
}

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

二、快速幂求逆元

1. 题目描述

给定 n 组 a i , p i a_i,p_i ai,pi,其中 p i p_i pi 是质数,求 a i a_i ai p i p_i pi 的乘法逆元,若逆元不存在则输出 impossible

注意:请返回在 0 ∼ p − 1 0∼p−1 0p1 之间的逆元。

乘法逆元的定义

若整数 b,m 互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(modm),则称 x 为 b 的模 m 乘法逆元,记为 b − 1 b^{−1} b1(modm)。
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时, b m − 1 b^{m-1} bm1 即为 bb 的乘法逆元。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个数组 a i , p i a_i,p_i ai,pi,数据保证 p i p_i pi 是质数。

输出格式

输出共 n 行,每组数据输出一个结果,每个结果占一行。

a i a_i ai p i p_i pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible

数据范围

1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105,
1 ≤ a i , p i ≤ 2 ∗ 1 0 9 1≤a_i,p_i≤2∗10^9 1ai,pi2109

输入样例:

3
4 3
8 5
6 3

输出样例:

1
2
impossible

2. 思路分析

在这里插入图片描述


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p)
{
    LL res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (LL)a % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n;
    cin >> n;
    while (n --)
    {
        int a, p;
        cin >> a >> p;
        if (a % p == 0) puts("impossible");
        else cout << qmi(a, p - 2, p) << endl;
    }
    return 0;
}

创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java技术一点通

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值