筛质数 + 质因子分解

试除法判断质数

最常用的方法就是用试除法,复杂度\(O(sqrt(N))\)
首先有下列几种写法:

  • 一、最暴力的写法,比较慢
bool is_prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i < n; i++)
        if(n % i == 0)
            return false;
    return true;    
}
  • 二、优化写法,但是由于每次执行\(sqrt(n)\),效率太低了
bool is_prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i < sqrt(n); i++)
        if(n % i == 0)
            return false;
    return true;     
}
  • 三、\(i*i\)可能会溢出 不好
bool is_prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i * i < n; i++)
        if(n % i == 0)
            return false;
    return true;     
}
  • 最好的方法:因为因数都是成对出现 因此可以直接枚举到\(n/i\)即可,并且这样一定不会溢出
bool is_prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i <= n / i; i++) //这样写一定不会溢出
        if(n % i == 0)
            return false;
    return true;     
}

质数筛:

埃拉托斯特尼筛法 O(loglogn):

  • 1.从前往后,把每一个数的倍数筛掉:
void get_primes1(int n) {
    for (int i = 2; i <= n; i++) {
        if(!st[i]) { //如果是质数
            primes[cnt++] = i;
        }
        for (int j = i + i; j <= n; j += i) st[j] = true; //把每一个数的倍数筛掉
    }
}
  • 2.再优化:把质数的倍数筛掉:
void get_primes2(int n) {
    for (int i = 2; i <= n; i++) {
        if(!st[i]) {
            primes[cnt++] = i;
            for (int j = i + i; j <= n; j += i) st[j] = true; //把每一个质数的倍数筛掉
        }
    }
}

代码:

using namespace std;

const int N = 1e6 + 10;
int primes[N], cnt;
bool st[N];

void get_primes1(int n) {
    for (int i = 2; i <= n; i++) {
        if(!st[i]) {
            primes[cnt++] = i;
        }
        for (int j = i + i; j <= n; j += i) st[j] = true; //把每一个数的倍数筛掉
    }
}

//再优化:把每一个质数的倍数筛掉即可,因此把循环放if里边即可
void get_primes2(int n) {
    for (int i = 2; i <= n; i++) {
        if(!st[i]) {
            primes[cnt++] = i;
            for (int j = i + i; j <= n; j += i) st[j] = true; //把每一个质数的倍数筛掉
        }
    }
}

int main() {
    int n;
    cin >> n;
    get_primes1(n);
    cnt = 0;
    get_primes2(n);
    cout << cnt << endl;
    system("pause");
    return 0;
}

但是这种方法有种缺陷,有些数会被重复筛选,例如26会被2筛掉,也会被13筛掉,因此为了改变这种缺陷我们可以用一种方法就是保证被筛掉的每一个数都是被它的最小质因子晒掉,那么复杂度就成了O(n),这就是欧拉筛法

欧拉筛选:

详细解释就看注释吧

#include <iostream>

using namespace std;

const int N = 100;
int primes[N], cnt;
bool st[N];

void get_primes1(int n) {
    for (int i = 2; i <= n; i++) {
        if(!st[i]) {
            primes[cnt++] = i;
        }    
        //枚举到n / i是因为:这里的primes[j]是最小质因子,i是某一个合数,i*primes[j]一定是满足<=x的,而且这样写可以防止越界
        for (int j = 0; primes[j] <= n / i; j++) { //从小到大枚举所有质数
            st[primes[j] * i] = true;
            if(i % primes[j] == 0) break; //代表primes[j]一定是i的最小质因子,
                                            //因此primes[j]一定是primes[j]*i的最小质因子,这里直接break也是为了避免重复筛选   
        }
    }
}   
//对于st[primes[j] * i] = true的成立性解释:
//如果i % primes[j] == 0
//代表primes[j]一定是i的最小质因子,因此primes[j]也一定是primes[j]*i的最小质因子,这样可以保证每个数只被晒一次(个人理解)

//如果i % primes[j] != 0   primes[j]一定小于i的所有质因子 primes[j]也一定是primes[j]*i的最小质因子
//因此筛的每一个数一定是用最小质因子来筛选掉 

int main() {
    int n;
    cin >> n;
    get_primes1(n);
    cout << cnt << endl;
    system("pause");
    return 0;
} 

最后补充一下质因子分解,这个知识点:

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

输入格式
第一行包含整数\(n\)

接下来\(n\)行,每行包含一个正整数\(a_i\)

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

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

数据范围
\(1≤n≤100\)
\(1≤a_i≤2∗10^9\)
输入样例:

2
6
8

输出样例:

2 1
3 1

2 3

首先看代码:

#include <iostream>

using namespace std;

void divide(int x) {
    for (int i = 2; i <= x; i++) {
        if(x % i == 0) {
            int s = 0;
            while(x % i == 0) { //求次数
                x /= i;
                s++;
            }
            cout << i << " " << s << endl;
        }
    }
}

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

这样写,第一眼可能觉得我们为什么不是从小到大枚举所有的质因数而是枚举所有数,这样做会不会被合数分解,答案是不会的:

这里不必专门从小到大枚举所有的质因数因为根据算术基本定理:任何一个大于\(1\)的自然数\(N\),如果\(N\)不为质数,那么\(N\)可以唯一分解成
有限个质数的乘积\(N=P_1^{a_1}*P_2^{a_2}*P_3^{a_3}......P_n^{a_n}\)

例如\(16\) 就会被分解成\(2*2*2*2\) 因此根本不会枚举到合数
因此当我们枚举到\(i\)的时候 \(x\)里就不包含有任何从\((2 - (n-1))\)的质因子了 因此只要\(x % i == 0\)成立那么i就一定是质数.

再优化:

  • 一个很重要的性质:x中只包含一个大于\(sqrt(n)\)的质因数 利用反证法证明:如果有两个大于\(sqrt(n)\)的质因数那么相乘就大于\(n\)了,
    就违背了算数基本定理,因此只需枚举到\(x / i\)即可,最后还要判断一下是否那唯一一个大于\(sqrt(n)\)的质因数。

代码:

#include <iostream>

using namespace std;

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;
    puts("");
}

int main() {
    int n;
    cin >> n;
    while(n--) {
        int x;
        cin >> x;
        divide(x);
    }
    system("pause");
    return 0;
} 
posted @ 2020-10-11 20:04  Xxaj5  阅读(189)  评论(0编辑  收藏  举报