ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

素数与筛法

2021-12-10 22:04:51  阅读:168  来源: 互联网

标签:prime cnt 筛法 int 合数 times 素数


定义

如果大于 \(1\) 的正整数 \(p\) 仅有正因子 \(1\) 和 \(p\) 则称 \(p\) 为素数,不是素数且大于 \(1\) 的整数为合数, \(1\) 既不是素数也不是合数

定理

  • 算术基本定理

    任何一个大于 \(1\) 的正整数 \(a\) 都能唯一分解为有限个质数的乘积,写作:

    \[a=p_1^{c_1}p_2^{c_2}\cdots p_n^{c_n} \]

    其中 \(c_i\) 都是正整数, \(p_i\) 都是素数

  • 质数分布定理

    对正实数 \(x\) ,定义 \(\pi(x)\) 为不大于 \(x\) 的素数个数,有 \(\pi(x)\approx\frac{x}{\ln x}\)

判定

如果一个正整数 \(a\) 为合数,则它必定存在一个因子 \(d\) ,且 \(2\leq d\leq \sqrt{a}\)

所以只要对 \(2\sim \sqrt{a}\) 之间的所有整数逐一判断是否是 \(a\) 的因子即可,时间复杂度为 \(O(n)\)

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

筛法

当我们想要知道 \(2\sim n\) 有哪些数是素数,哪些数是合数时,一个自然的想法是对所有数进行一次素数判定,但这种做法时间复杂度为 \(O(n\sqrt{n})\) ,显然不是理想的复杂度

埃氏筛

用一个数组 bool is_prime[i] 表示 \(i\) 是否为一个素数,先假设所有数都是素数,从小到大枚举每一个素数 \(i\) ,把 \(i\) 的倍数(除了本身)都标记为合数。如何枚举质数 \(i\) 呢?当从小到大遍历到一个数 \(i\) 时,若它尚未被标记,则它不能被 \(2\) 到 \(i-1\) 的任何数整除,说明该数为素数

另外,可以发现 \(2\) 和 \(3\) 都会把 \(6\) 标记为合数,因为小于 \(i^2\) 的 \(i\) 的倍数在遍历更小的数时已经被标记过了。所以对于每个 \(i\) ,把大于等于 \(i^2\) 的倍数标记为合数即可,时间复杂度为 \(O(n\log\log n)\)

const int MAX_N = 100000 + 5;
bool is_prime[MAX_N];

void sieve(int n)
{
    memset(is_prime, 1, sizeof(is_prime));
    is_prime[1] = false;
    for(int i = 2; i <= n; i++) {
        if(!is_prime[i])
            continue;
        for(int j = i; j * i <= n; j++)
            is_prime[i * j] = false;
    }
}
线性筛

虽然埃氏筛已经达到了较优的复杂度,但仍有优化空间。我们发现,埃氏筛法即使在优化后仍存在重复标记合数的问题,比如 \(12=2\times 6=3\times 4\) ,被标记了两次,其根本原因是埃氏筛不能唯一确定一个合数被标记的方式

所以我们设法让每个合数只被其最小素因子标记,用一个数组 int prime[i] 表示第 \(i\) 个素数的值, 用 bool is_prime[i] 表示 \(i\) 是否为素数,用 int cnt 表示当前筛选出了多少素数,以下算法时间复杂度为 \(O(n)\)

const int MAX_N = 100000 + 5;
bool is_prime[MAX_N];
int prime[MAX_N];
int cnt;

void sieve(int n)
{
    memset(is_prime, 1, sizeof(is_prime));
    is_prime[1] = false;
    cnt = 0;
    for(int i = 2; i <= n; i++) {
        if(is_prime[i])
            prime[++cnt] = i;
        for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            is_prime[i * prime[j]] = false;
            if(i % prime[j] == 0)
                break;
        }
    }
}

其中 break 是为了保证 一个合数只会被它最小的素因子筛去 ,我们可以做简单证明:

若 \(i\bmod prime[j] = 0\) ,即 \(prime[j]\mid i,i=k\times prime[j]\)

则 \(\forall t>j, i\times prime[t]=prime[j]\times(k\times prime[t])\)

显然我们可以找到 \(i\times prime[t]\) 的更小素因子 \(prime[j]\) ,所以不应由 \(prime[t]\) 将其筛去

题目

1.NOIP2012 质因数分解

遍历 \(2\) 到 \(\sqrt{n}\) 寻找 \(n\) 的较小因子 \(a\) ,较大因子即为 \(\frac{n}{a}\)

2.CF776B Sherlock

当 \(n\leq 2\) 时,只需要染一种颜色,因为 \(2\) 不是 \(3\) 的素因子

当 \(n\geq 3\) 时,只需要染两种颜色,我们把所有素数都染成颜色 \(1\) ,所有合数都染成颜色 \(2\) ,即满足题意,用线性筛筛出 \(2\) 到 \(n + 1\) 的所有素数即可

3.BZOJ1607 轻拍牛头

对于 \(A_i\) ,如果 \(A_j\mid A_i,i\neq j\) ,那 \(A_i\) 就要拍 \(A_j\)

用数组 int cnt[i] 表示 \(i\) 在序列中出现的次数, int ans[i] 表示序列值为 \(i\) 的牛要拍的次数,对于每个 \(A_i\) ,枚举它的倍数 \(i\times j\) ,如果 \(i\times j\) 在序列中出现,那么序列值为 \(i\times j\) 的牛一定会拍所有序列值为 \(i\) 的牛,所以 ans[i * j] += cnt[i]

#include<bits/stdc++.h>
using namespace std;

int n, maxx = 0, minn = 1e9;
int a[100000 + 5];
int cnt[1000000 + 5], ans[1000000 + 5];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        minn = min(minn, a[i]);
        maxx = max(maxx, a[i]);
        cnt[a[i]]++;
    }
    
    for(int i = minn; i <= maxx; i++)
        if(cnt[i])
            for(int j = 1; i * j <= maxx; j++)
                if(cnt[i * j])
                    ans[i * j] += cnt[i];

    for(int i = 1; i <= n; i++)
        cout << ans[a[i]] - 1 << endl;
    return 0;
}
4.BZOJ2721 樱花

考虑将题目中的式子变形,通分得:

\[\frac{xy}{x+y}=n! \]

移项得:

\[xy-(x+y)n!=0 \]

观察出式子此时变为了类似十字相乘得形式,两边同时加上 \(n!\) 得:

\[(x-n!)(y-n!)=(n!)^2 \]

所以 \(x-n!\) 是 \((n!)^2\) 的约数,确定了 \(x\) 即可确定 \(y\) ,那么问题就转化为了求 \((n!)^2\) 的约数个数

如果 \(n!=p_1^{c1}p_2^{c2}\cdots p_i^{c_i}\) ,则 \((n!)^2=p_1^{2c1}p_2^{2c2}\cdots p_i^{2c_i}\) ,答案为:

\[\prod_{j=1}^i (2c_j+1) \]

对于 \(c_j\) ,有如下公式:

\[c_j=\sum_{l=1}^{\infty}\lfloor\frac{n}{p_j^l}\rfloor \]

可以给出证明:

对 \(2\sim n\) 全部使用唯一分解定理进行分解, \(c_j\) 即为这 \(n-1\) 个分解式中 \(p_j\) 的指数和

设其中 \(p_j\) 的指数为 \(r\) 的分解式有 \(m_r\) 个,可得:

\[\begin{aligned} c_j&=m_1+2m_2+3m_3+\cdots\\ &=(m_1+m_2+m_3+\cdots)+(m_2+m_3+m_4+\cdots)+\cdots\\ &=\lfloor\frac{n}{p_j}\rfloor+\lfloor\frac{n}{p_j^2}\rfloor+\lfloor\frac{n}{p_j^3}\rfloor+\cdots\\ &=\sum_{l=1}^{\infty}\lfloor\frac{n}{p_j^l}\rfloor \end{aligned} \]

所以用线性筛筛出 \(2\sim n\) 的所有素数,代入上述公式即可得到答案

#include<bits/stdc++.h>
using namespace std;

const int MOD = 1000000000 + 7;
const int MAX_N = 1000000 + 5;
int n, cnt;
long long ans = 1;
bool is_prime[MAX_N];
int prime[MAX_N];

void sieve(int n)
{
    memset(is_prime, 1, sizeof(is_prime));
    is_prime[1] = false;
    cnt = 0;
    for(int i = 2; i <= n; i++) {
        if(is_prime[i])
            prime[++cnt] = i;
        for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            is_prime[i * prime[j]] = false;
            if(i % prime[j] == 0)
                break;
        }
    }
}

int main()
{
    cin >> n;
    if(n == 1) {
        cout << 0 << endl;
        return 0;
    }
    sieve(n);
    for(int i = 1; i <= cnt; i++) {
        int t = 0;
        for(long long j = prime[i]; j <= n; j *= prime[i])
            t = t + n / j;
        ans = (2 * t + 1) * ans % MOD;
    }
    cout << ans << endl;
    return 0;
}

标签:prime,cnt,筛法,int,合数,times,素数
来源: https://www.cnblogs.com/tttkf/p/15673977.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有