算法基础课—数学知识(一)质数和约数

本文介绍了算法基础中的数学概念,包括质数的判断方法(试除法优化)、分解质因数、埃氏筛法求质数、线性筛法以及欧几里得算法求最大公约数。通过实例和模板代码演示,深入浅出地讲解了这些关键知识点及其时间复杂度分析。
摘要由CSDN通过智能技术生成

质数

判断质数

试除法

从2到n进行遍历,看是否存在整数能整除,如果有则是质数,没有则不是

优化后的试除法

从2到根号n进行遍历,看是否存在整数能整除,如果有则是质数,没有则不是

如果存在大于根号n的数能整除其,必一定存在另外一个与之配对的小于根号n的数能整除其,所以,可以缩小区间到根号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;
}

代码

#include <iostream>
using namespace std;
bool isPrime(int x){
    int i;
    if(x < 2) return false;
    for(i = 2; i <= x / i; i ++){
        if(x % i == 0) return false;
    }
    return true;
}
int main(){
    int n, x, i;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>x;
        if(isPrime(x)) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}

试除法分解质因数

求分解质因数是在于求
其质数的表达式
即一个数一定可以有素数集合唯一表达
在这里插入图片描述
而求素数的分解质因数,即求,其对应的p1,p2…,以及其p1,p2的指数幂
如 6 ——2 1/3 1
算法思想是从2开始循环,如果可以整除2,则一直整除2直到不能整除为止,记录其整除的次数,即为指数幂,然后再i++,判断是否可以整除3,如果可以重复上述操作,如果不行,则继续i++。直到i==n。找出所有分解质因数

·由于合数可以被质数整除,所以很多合数都被while循环中过滤掉了,所以枚举的i一定是质数

时间复杂度O(n)

优化后的分解质因数

改变:原来是i从2枚举到n,后面i从2,枚举到根号n(i <= n / i)
依据的数学定理:n中只包含一个大于sqrt(n)的质因子
所以最后如果到根号n循环结束之后,x仍然大于1,则说明其是那个大于根号n的质因子,输出即可。
于是我们可以先再根号n之前的枚举,如果除到最后n>1,说明他就是那个大于根号n的质因子

这样时间复杂度降到O(sqrt(n))

时间复杂度,介于logn和根号n之间

 如果n==2的k次方,那么i等于2是,n一次性在while循环中执行logn次,然后n==1,则结束了所以是logn

在这里插入图片描述

模板

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;
}

代码

#include <iostream>
using namespace std;
void divide(int x){
    int i;
    for(i = 2; i <= x / i; i ++){
        if(x % i == 0){
            int s = 0;
            while(x % i == 0){
                x = x / i;
                s++;
            }
            cout<<i<<" "<<s<<endl;
        }
    }
    if(x > 1) cout<<x<<" 1"<<endl;
    cout<<endl;
}
int main(){
    int n, i, x;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>x;
        divide(x);
    }
}

筛质数

思想: 依次把所有的数的倍数删掉,剩下的就都是质数

埃氏筛法(时间复杂度O(nloglogn))

依次把所有的数的倍数删掉,剩下的就都是质数

在这里插入图片描述

时间复杂度的计算

在这里插入图片描述
外层循环n,内层循环对应n中i的倍数有几个,为n/i个,全部加起来。

模板

用st数组表示是否筛去,primes记录保存下来的素数,如果遍历到自身仍然没有被筛去,说明时素数,所以在一开始用if语句判断,然后将其的倍数都删去。

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;
    }
}

代码

//埃氏筛法
#include <iostream>
using namespace std;
const int N = 1e6+ 10;
int primes[N];
bool st[N];
int cnt;
int get_primes(int n){
    int i, j;
    for(i = 2; i <= n; i ++){
        if(!st[i]){
            primes[cnt++] = i;
           for (j = i + i; j <= n; j += i){
                st[j] = true;
            }
        }
    }
    return cnt;
}

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

线性筛法

关键:每个数字都只会它的最小质因数筛掉
列举了两种情况
在这里插入图片描述

第一种情况 :如果i % pj == 0,那么pj一定是i的最小因子,所以pj也一定是pj*i的最小因子
第二种情况:i % pj != 0,那么pj一定小于i的所有质因子,所以pj一定是pj i的最小质因子
思路:每次筛除pj
i 这个数,由于一个数一定是被前面一个这样的删掉的,所以只可能被删除一次,所以是线性的

由于一个数一定是被其最小质因数筛去的,所以具有唯一性,时间复杂度为O(n)

代码
遍历到自身,先判断是否被筛去,如果没有则说明是素数,加入primes集合中
然后针对当前i的情况,判断素数集合中,各个素数和i相乘的数的情况
如果 i 能够被素数集合中的primes[j]整除,说明primes[j] 是当前的最小质因数,于是break,因为后续的i * primes【j】的最小质因数都为之前的primes【j】,而不是当前这个,所以无法删去,所以这里可以直接break
如果i不能够整除,说明i的最小质因数比pj大,于是pj*i的最小质因数是pj,于是可以删去

在这里能够删去的,就是pj是当前pj * i最小质因数的情况。
在这里插入图片描述

模板

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;
        }
    }
}


代码

//埃氏筛法
#include <iostream>
using namespace std;
const int N = 1e6+ 10;
int primes[N];
bool st[N];
int cnt;
int get_primes2(int n){
    int i, j;
    for(i = 2; i <= n; i ++){
        if(!st[i])
            primes[cnt++] = i;
        for(j = 0; primes[j] <= n / i; j ++ ){
            st[primes[j] * i] = true;
            if(i % primes[j] == 0) break;
        }
    }
    return cnt;
}
int main(){
    int n;
    cin>>n;
    cout<<get_primes2(n)<<endl;
    
}

约数

试除法——求所有约数

中间那一个是避免重复压入,如果i==n/i,即i平方等于n,则会重复压入两个相同的数

思想:从i=1 遍历到根号n,然后如果x可以整除i,则将i 和x / i压入,注意不要重复压入。
到根号n是因为一个数一定由一个大于n的数和小于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;
}

代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> get_divisors(int x){
    vector<int> res;
    int i;
    for(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;
}
int main(){
    int n, i, j, x;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>x;
        auto result = get_divisors(x);
        for(j = 0; j < result.size(); j ++)
            cout<<result[j]<<" ";
        printf("\n");
    }
}

求约数个数

一个数一定可以由这样的情况唯一表示
其质数的表达式
即一个数一定可以有素数集合唯一表达
在这里插入图片描述
所以其数和约数都可以由这样唯一表示,区别在于指数幂次的不同
由排列组合发现,其约数可以有(α1+1)(α2+1)…(αk+1)种排列组合构成
于是约数个数即为其
在这里插入图片描述

所以求一个约数的个数,先求其素数表达形式,即先进行分解质因数,然后再根据公式求解器约数个数。

求约数个数那道题
先把a1…an转换为p1。。。pn素数的那个公式(方法,针对每个a分解质因数,用一个整体的map存起来,然后在对下一个a,用map累加起来)
然后在遍历map 求那个公式

代码

题目

给定 n 个正整数 ai,请你输出这些数的乘积的约数个数,答案对 109+7 取模。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7 取模。

数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
2
6
8
输出样例:
12

代码
#include <iostream>
#include <unordered_map>
using namespace std;
const int N = 110, INF = 1e9 + 7;
int a[N];
unordered_map<int, int> primes;
void divide(int x){
    int i;
    for(i = 2; i <= x / i; i ++){
        while(x % i == 0){
            x = x / i;
            primes[i]++;
        }
    }
    if(x > 1) primes[x]++;
}
int main(){
    int n, i;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>a[i];
        divide(a[i]);
    }
    long long res = 1;//用longlong定义,如果用int很容易溢出
    for (auto p : primes) res = res * (p.second + 1) % INF;
    //这里是边做乘法边取模,如果对最后答案取模。先mod和后mod对结果是不影响的,但是有可能结果溢出变成负数,所以要先mod
    //res = res % INF;
    cout<<res<<endl;
    
}

求约数之和

约数之和
即将各个排列组合拆开
在这里插入图片描述

代码

题目

给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

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

数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
2
6
8
输出样例:
252

代码
#include <iostream>
#include <unordered_map>
using namespace std;
const int N = 110, INF = 1e9 + 7;
int a[N];
unordered_map<int, int> primes;
void divide(int x){
    int i;
    for(i = 2; i <= x / i; i ++){
        while(x % i == 0){
            x = x / i;
            primes[i]++;
        }
    }
    if(x > 1) primes[x]++;
}
int main(){
    int n, i;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>a[i];
        divide(a[i]);
    }
    long long res = 1,s = 0, tt = 1;;//用longlong定义,如果用int很容易溢出
    for (auto p : primes){
        int a = p.first, b = p.second;
        long long t = 1;
        while (b -- ) t = (t * a + 1) % INF;//求等比数列的一种方式
        res = res * t % INF;
        //res = res % INF;
    }
    //这里是边做乘法边取模,如果对最后答案取模。先mod和后mod对结果是不影响的,但是有可能结果溢出变成负数,所以要先mod
    //res = res % INF;
    cout<<res<<endl;
    
}

最大公约数——欧几里得算法

欧几里得算法——辗转相除法——求最大公约数
关键:a和b的最大公约数会等于b和a mod b的最大公约数
证明:a可以整除d,b可以整除d,则ax+by可以整除d。
于是要证a和b的最大公约数== b和a mod b的最大公约数
a mod b = a - c * b。c=如下图所示
于是如果a可以整除d,b可以整除d,则a-c*b也可以整除d,于是他们的所有公约数都相同,所以他们拥有最大公约数也相同。
在这里插入图片描述

时间复杂度logn

模板

如果b不等于0,那么返回b,a mod b。
如果b等于0, 则返回a。

0和任意数j的最大公约数是这个任意数j。

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

代码

题目

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

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数对 ai,bi。

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

数据范围
1≤n≤105,
1≤ai,bi≤2×109
输入样例:
2
3 6
4 6
输出样例:
3
2

代码
#include <iostream>
using namespace std;
int gcb(int a, int b){
    return b? gcb(b, a % b) : a;
}
int main(){
    int i, n, a, b;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>a>>b;
        cout<<gcb(a, b)<<endl;
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值