跳转至

筛法

质数筛法

定义

OIP-C.Vm205xkbzhUHc5d9cW-T6QHaHC

朴素筛法

对于每个数,筛掉他的所有倍数

复杂度:2要筛n / 2 次,3要筛n / 3次,所以一共要筛n / 2 + n / 3 + …n / n,也就是 n * (1 / 2 + 1 / 3 + … + 1 / n)次,后面求和是调和级数,当 n 足够大时,等于lnn + c,c是常数,所以复杂度为 logn * n.

埃氏筛法

v2-06e72db7050d7492b1f5c253675618fa_r.jpg

复杂度:质数定理:1 - n 中质数的数量是 n / lnn 个,所以埃氏筛法的复杂度为 n * loglog n,很接近线性了。

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,cnt,prime[100001],notprime[100001];

void erat(int n){
    for(int i=2;i<=n;i++)
    if(!notprime[i]){
        prime[++cnt]=i;
        for(int j=2;j<=n/i;j++){
            notprime[j*i]=1;
        }
    }
}
signed main(){
    cin>>n;
    erat(n);
    for(int i=1;i<=cnt;i++)cout<<prime[i]<<' ';
}

线性筛

线性筛法的核心是:n 只会被最小质因子筛掉,复杂度 O(n) 。

那么这是怎么做到的?

我们首先从2开始枚举每一个数,记\(ispri_i\)为数字i的最小因子(不含1).

\(ispri_i=0\),说明i在之前都没有遇到它的因子,即i是质数。

无论i是否为质数,我们都需要使用它来标记一些数。什么从小到大枚举质数表里的质数\(pri_j\),并且将\(i\times pri_j\)标记为非质数。此时\(pri_j\)也是它的ispri(最小真因数)。当我们枚举的\(pri_j>ispri_i\)时,我们就应该停止标记,因为后面的数显然可以被ispri_i标记。

为什么这样标记就可以做到不重复不遗漏呢?首先因为\(pri_j>ispri_i\)时,我们就停止标记,所以不会重复标记。又因为对于一个合数x,记其最小质因数为p,那么我们一定会在枚举到x/p时将x标记。因为\(ispri_{x/p}≥p\),所以我们一定会枚举到p。

为了降低时间复杂度,我们在\(pri_j>N/i\)时也停止。

R-C.2dd03097fb6e338bd01f91c7243e1fc6

例如 在埃氏筛中 6 会被 2 和 3 都筛一遍,增加了复杂度

代码中两种情况的解释:

当 i % pj = 0 时,$p_j $一定是 $i $ 的最小质因子,因此 \(p_j\) 一定是 \(p_j\) * i 的最小质因子。 当 i % pj ≠ 0 时,\(p_j\) 一定小于 $ i$ 的最小质因子,因此 \(p_j\)一定是 \(p_j\) * i 的最小质因子。

原文链接:https://blog.csdn.net/magnte/article/details/119805672

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,cnt,prime[100001],notprime[100001];

void oula(int n){
    for(int i=2;i<=n;i++){

        if(!notprime[i]){
            prime[++cnt]=i;
            notprime[i]=i;
        }
        for(int j=1;j<=cnt;j++){
            if(prime[j]>notprime[i]||prime[j]>n/i)break;
            notprime[i*prime[j]]=prime[j];
        } 
    }
}
signed main(){
    cin>>n;
    oula(n);
    for(int i=1;i<=cnt;i++)cout<<prime[i]<<' ';
}

notprime[i] = i时,说明i是质数,反之存i的最小质因子.

i从 \(2 \to n\) 递增, 每次到达一个 \(i\) , 筛去数 \(i \times p\) (即将notprime[i*p]赋值为p), 其中 \(p≤notprime[i]\) 且为质数

image.png

优化算法

例题

【模板】线性筛素数 - 洛谷

算法

【算法/数论】欧拉筛法详解:过程详述、正确性证明、复杂度证明_seh_sjlj的博客-CSDN博客

#include<bits/stdc++.h>
using namespace std;
const int N=1e8+5;
int notprime[N],q;
bool isprime[N]; // isprime[i]表示i是不是素数
int prime[N]; // 现在已经筛出的素数列表
int n; // 上限,即筛出<=n的素数
int cnt; // 已经筛出的素数个数

void euler()
{
    memset(isprime, true, sizeof(isprime)); // 先全部标记为素数
    isprime[1] = false; // 1不是素数
    for(int i = 2; i <= n; ++i) // i从2循环到n(外层循环)
    {
        if(isprime[i]) prime[++cnt] = i;
        // 如果i没有被前面的数筛掉,则i是素数
        for(int j = 1; j <= cnt && i * prime[j] <= n; ++j)
        // 筛掉i的素数倍,即i的prime[j]倍
        // j循环枚举现在已经筛出的素数(内层循环)
        {
            isprime[i * prime[j]] = false;
            // 倍数标记为合数,也就是i用prime[j]把i * prime[j]筛掉了
            if(i % prime[j] == 0) break;
            // 最神奇的一句话,如果i整除prime[j],退出循环
            // 这样可以保证线性的时间复杂度
        }
    }
}

signed main(){
    cin>>n>>q;
    euler();
    while(q--){
        int k;
        cin>>k;
        cout<<prime[k]<<endl;
    }
}

image.png

欧拉函数筛

欧拉函数

\(\varphi(n)\),表示的是小于等于 n 且和 n 互质的数的个数。 比如说 \(\varphi(1) = 1\)

若 m 是素数,则\(\varphi(m)=m-1\)

若m=pq,其中p,q是质数且p≠q,则\(\varphi(m)=(p-1)(q-1)\)

\(m=\prod p_i^{l_i}\),即质因数分解,那么\(\varphi(m)=m\times \prod (1-\frac{1}{p_i})\)

欧拉函数是**积性函数**。 即对任意满足\(\gcd(a,b)=1\)的整数a,b,有\(\varphi(ab)=\varphi(a)\varphi(b)\)

求单点欧拉函数

int getphi(int x){
    int res=x;
    for(int i=2;i*i<=x;i++){
        if(x%i==0){
            res=res/i*(i-1);
            while(x%i==0)x/=i;
        }
    }
    if(x>1)res=res/x*(x-1);
    return res;
}

//筛出质数后的版本

int phi(int x){
    //按题目特判x=1的情况!
    int t=x;
    int a=1,b=1;
    for(int i=1;i<=tot;i++){
        if(pri[i]*pri[i]>x)break;
        if(x%pri[i])continue;
        a*=(pri[i]-1);
        b*=(pri[i]);
        while(x%pri[i]==0)x/=pri[i];
    }
    if(x>1){
        a*=(x-1);
        b*=x;
    }

    return t/b*a;
}

筛法求欧拉函数

什么考虑用线性筛来快速求出\(1\sim n\)之间的欧拉函数。如果x是质数,那么\(\varphi(x)=x-1\)。否则我们考虑在筛到\(pri_j\times i=x\)时将\(\varphi(x)\)求出来。

因为欧拉函数是极性函数,所以当i和\(pri_j\)互质时,\(\varphi(x)=\varphi(i)\times \varphi(pri_j)\)。那么当i和\(pri_j\)不互质时,就到了我们的退出条件(因为我们是从小到大枚举pri,因此第一次遇到不互质时说明\(ispri_i=pri_j\),再往后就是\(ispri_i>pri_j\)了),此时\(\varphi(x)=\varphi(i)\times pri_j\)。为什么呢?

\(\varphi(x)=x\times \prod (1-\frac{1}{p_i})=pri_j\times i\times \prod (1-\frac{1}{p_i})=pri_j\times \varphi(i)\),注意这里的\(\prod (1-\frac{1}{p_i})\)和因子(\(p_i\))的个数无关,只和它的值的种类有关,又因为x,i的因子的值的种类相同,所以上式成立。

int prime[N],notprime[N];
int cnt;
int phi[N];

void init(){

    phi[1]=1;
    for(int i=2;i<N;i++){
        if(!ispri[i]){
            ispri[i]=i;
            pri[++tot]=i;
            phi[i]=i-1;
        }

        for(int j=1;j<=tot;j++){
            if(pri[j]*i>=N)break;
            if(i%pri[j]==0){
                phi[i*pri[j]]=phi[i]*pri[j];
                ispri[i*pri[j]]=pri[j];
                break;
            }

            ispri[i*pri[j]]=pri[j];
            phi[i*pri[j]]=phi[i]*phi[pri[j]];

        }
    }
}

例题 #1 [HAOI2012] 外星人

题目描述

艾莉欧在她的被子上发现了一个数字 \(N\),她觉得只要找出最小的 \(x\) 使得,\(\varphi^x(N) = 1\)。根据这个 \(x\) 她就能找到曾经绑架她的外星人的线索了。当然,她是不会去算,请你帮助她算出最小的 \(x\)

输入格式

第一行一个正整数 \(\mathrm{test}\),接下来 \(\mathrm{test}\) 组数据每组数据第一行一个正整数 \(m\),接下来 \(m\) 行每行两个正整数 \(p_i, q_i\)

其中 \(\displaystyle \prod_{i = 1}^{m} p_i^{q_i}\)\(N\) 的标准分解形式。

\(\prod\) 为连乘

\(\varphi^x(N)\) 表示嵌套 \(x\) 次,不是幂

输出格式

输出 \(\mathrm{test}\) 行,每行一个整数,表示答案。

\(100\%\) 的数据,\(\mathrm{test} \le 50\)\(1 \le p_i \le {10}^5\)\(1 \le q_i \le {10}^9\)\(m \le 2000\)

\(\varphi\) 为欧拉函数,\(\varphi(n)\) 即小于等于 \(n\) 的数中与 \(n\) 互质的数的个数。

提示:\(\varphi(\prod_{i=1}^mp_i^{q_i})=\prod_{i=1}^m(p_i-1)*p_i^{q_i-1}\)


首先我们就直接考虑对\(D=\prod_{i=1}^m(p_i-1)*p_i^{q_i-1}\)做到最小的x使得\(\varphi^x(D)=1\)

那么我们发现D的因数都在10^5为内,我们可以考虑用欧拉函数的唯一分解定理式求解。我们发现递归的本质是每次执行下面的操作:

对于D的每一个质因数\(p^q\),将其变成\((p-1)\times p^{q-1}\)。注意-1后的数不再是质数。问多少次操作可以使得D变成2或者1.

模拟是不可能的,并且因为一个数-1或变成哪些数字也需要很高的复杂度来统计,所以我们需要转换思路。

我们考虑若最开始的D不是2的倍数,那么从第2次操作开始一定又2的倍数,否则从第一次开始就有。并且每一次操作最多消去一个2,所以我们只需要统计一开始的D的所有因数可以产生多少个2即可。

那么我们设\(f_i\)为因数i可以产生的2的个数,那么若i是质数,则\(f_i=f_{i-1}\)。因为i自己不可被分解,只有-1后变成i-1才可以被分解,这和i-1直接分解是一样的。

如果i不是质数,那么设\(i=a\times b\),那么\(f_i=f_a+f_b\)。为什么呢?还是横显然的吧。

因此我们像筛phi一样筛f即可。最后\(p^q\)的贡献为\(f_p\times q\)。注意,如果存在p=2,那么答案-1.

不要忘记最开始的那次操作。

/*                                                                                
                      Keyblinds Guide
                    ###################
      @Ntsc 2024

      - Ctrl+Alt+G then P : Enter luogu problem details
      - Ctrl+Alt+B : Run all cases in CPH
      - ctrl+D : choose this and dump to the next
      - ctrl+Shift+L : choose all like this
      - ctrl+K then ctrl+W: close all
      - Alt+la/ra : move mouse to pre/nxt pos'

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

#define rep(i, l, r) for (int i = l, END##i = r; i <= END##i; ++i)
#define per(i, r, l) for (int i = r, END##i = l; i >= END##i; --i)
#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first

// #define innt int
#define itn int
// #define inr intw
// #define mian main
// #define iont int

#define rd read()
int read(){
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
      xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}

#define ell dbg('\n')
const char el='\n';
const bool enable_dbg = 1;
template <typename T,typename... Args>
void dbg(T s,Args... args) {
    if constexpr (enable_dbg){
    cerr << s;
    if(1)cerr<<' ';
        if constexpr (sizeof...(Args))
            dbg(args...);
    }
}

#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }
#else
#define dbg(...)
#endif


const int N = 1e6 + 5;
const int INF = 1e18;
const int M = 1e5;
const int MOD =999911659;


int f[N],pri[N],ispri[N];
int tot;



void init(){
    f[1]=1;

    for(int i=2;i<N;i++){
        if(!ispri[i]){
            ispri[i]=i;
            pri[++tot]=i;
            f[i]=f[i-1];
        }
        for(int j=1;j<=tot;j++){
            if(pri[j]*i>=N)break;
            f[i*pri[j]]=f[pri[j]]+f[i];
            ispri[i*pri[j]]=pri[j];
            if(i%pri[j]==0){
                break;
            }
        }
    }
}


itn ans=0;

void solve(){
    int m=rd;
    ans=0;
    for(int i=1;i<=m;i++){
        int p=rd,q=rd;
        if(p==2)ans--;
        ans+=f[p]*q;
    }   

    cout<<ans+1<<endl;

}




signed main() {
    // freopen(".in","r",stdin);
    // freopen(".in","w",stdout);

    init();
    int T=rd;
    while(T--){
        solve();
    }
    return 0;
}

莫比乌斯函数筛

莫比乌斯函数

\(\mu(x)=\begin{cases}0 &\exist d_i\geq 1 \\(-1)^k & \forall d_i=1\end{cases}\)\(d_i\)是x的质因子的次幂,k是x的质因子的个数。