首页 > 其他分享 >筛法

筛法

时间:2023-06-04 22:44:15浏览次数:41  
标签:lfloor right frac 筛法 int sum left

埃氏筛

  • 如何求出\(1\sim n\)中有几个质数?

考虑这样一件事情:对于任意一个大于\(1\)的正整数\(n\),那么它的\(x\)倍就是合数\((x > 1)\)。利用这个结论,我们可以避免很多次不必要的检测。

如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

void Eratosthenes(){
    for(int i=2;i<=n;i++)
        if(!v[i]){
            prime[++cnt]=i;
            if(i*i>n) continue;
            for(int j=i*i;j<=n;j+=i)
                v[j]=1;
        }
}

其时间复杂度为$ O(n\log\log n)$。

  • 如何优化?

筛至平方根

显然,要找到直到\(n\)为止的所有素数,仅对不超过\(\sqrt n\)的素数进行筛选就足够了。

只筛奇数

因为除 2 以外的偶数都是合数,所以我们可以直接跳过它们,只用关心奇数就好。

首先,这样做能让我们内存需求减半;其次,所需的操作大约也减半。

减少内存的占用

我们注意到筛法只需要\(n\)比特的内存。因此我们可以通过将变量声明为布尔类型,只申请\(n\)比特而不是$n \(字节的内存,来显著的减少内存占用。即仅占用\)\frac{n}{8}$ 字节的内存。

但是,这种称为位级压缩的方法会使这些位的操作复杂化。任何位上的读写操作都需要多次算术运算,最终会使算法变慢。

因此,这种方法只有在\(n\)特别大,以至于我们不能再分配内存时才合理。在这种情况下,我们将牺牲效率,通过显著降低算法速度以节省内存(减小8倍)。

当然,可以用 vector<bool>bitset<>

分块筛选

由优化「筛至平方根」可知,不需要一直保留整个 is_prime[1...n] 数组。为了进行筛选,只保留到\(\sqrt n\)的素数就足够了,即 prime[1...sqrt(n)]。并将整个范围分成块,每个块分别进行筛选。这样,我们就不必同时在内存中保留多个块,而且 CPU 可以更好地处理缓存。

设\(s\)是一个常数,它决定了块的大小,那么我们就有了\(\lceil {\frac n s} \rceil\)个块,而块\(k(k = 0 \dots \lfloor {\frac n s} \rfloor)\)包含了区间\([ks, ks + s - 1]\)中的数字。我们可以依次处理块,也就是说,对于每个块\(k\),我们将遍历所有质数(从\(1\)到\(\sqrt n\))并使用它们进行筛选。

值得注意的是,我们在处理第一个数字时需要稍微修改一下策略:首先,应保留$[1, \sqrt n] $中的所有的质数;第二,数字\(0\)和\(1\)应该标记为非素数。在处理最后一个块时,不应该忘记最后一个数字\(n\)并不一定位于块的末尾

int Eratosthenes(int n){
    const int S=10000;
    int nsqrt=sqrt(n);
    vector <int> prime;
    vector <bool> v(nsqrt+1,1);
    for(int i=2;i<=nsqrt;i++)
        if(v[i]){
            prime.push_back(i);
            for(int j=i*i;j<=nsqrt;j++) v[j]=0;
        }
    int res=0;
    vector <bool> block(S);
    for(int k=0;k*S<=n;k++){
        fill(block.begin(),block.end(),1);
        int start=k*S;
        for(int p:prime){
            int started=(start+p-1)/p;
            int j=max(started,p)*p-start;
            for(;j<S;j+=p) block[j]=0;
        }
        if(k==0) block[0]=block[1]=0;
        for(int i=0;i<S&&start+i<=n;i++)
            if(block[i]) res++;
    }
    return res;
}

分块筛法的渐进时间复杂度与埃氏筛法是一样的(除非块非常小),但是所需的内存将缩小为\(O(\sqrt{n} + S)\),并且有更好的缓存结果。 另一方面,对于每一对块和区间\([1, \sqrt{n}]\)中的素数都要进行除法,而对于较小的块来说,这种情况要糟糕得多。 因此,在选择常数 \(S\) 时要保持平衡。

块大小$ S$取\(10^4 \sim 10^5\)之间,可以获得最佳的速度。

线性筛

埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。

如果能让每个合数都只被标记一次,那么时间复杂度就可以降到$ O(n)$了。

void sieve(){
    for(int i=2;i<=n;i++){
        if(!v[i]){prime[++cnt]=i;}
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
            v[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}

注意到筛法求素数的同时也得到了每个数的最小质因子。

  • 筛法求欧拉函数

注意到在线性筛中,每一个合数都是被最小的质因子筛掉。比如设\(p_1\)是\(n\)的最小质因子,\(n' = \frac{n}{p_1}\),那么线性筛的过程中\(n\)通过\(n' \times p_1\)筛掉。

观察线性筛的过程,我们还需要处理两个部分,下面对\(n' \bmod p_1\)分情况讨论。

如果\(n' \bmod p_1 = 0\),那么\(n'\)包含了\(n\)的所有质因子。

\[\begin{aligned} \varphi(n) & = n \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}} \\\\ & = p_1 \times n' \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}} \\\\ & = p_1 \times \varphi(n') \end{aligned} \]

那如果\(n' \bmod p_1 \neq 0\)呢,这时\(n'\)和\(p_1\)是互质的,根据欧拉函数性质,我们有:

\[\begin{aligned} \varphi(n) & = \varphi(p_1) \times \varphi(n') \\\\ & = (p_1 - 1) \times \varphi(n') \end{aligned} \]

void sieve(){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!v[i]){prime[++cnt]=i;phi[i]=i-1;}
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
            v[i*prime[j]]=1;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*phi[prime[j]];
        }
    }
}

  • 筛法求莫比乌斯函数

根据莫比乌斯函数的定义,设\(n\)是一个合数,$p_1 \(是\) n \(的最小质因子,\)n'=\frac{n}{p_1}$,有:

\[\mu(n)= \begin{cases} 0 & n' \bmod p_1 = 0\\\\ -\mu(n') & \text{otherwise} \end{cases} \]

若$ n \(是质数,有\) \mu(n)=-1$。

void sieve(){
    mu[1]=1;
    for(int i=2;i<=n;i++){
        if(!v[i]){prime[++cnt]=i;mu[i]=-1;}
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
            v[i*prime[j]]=1;
            if(i%prime[j]==0) break;
            mu[i*prime[j]]=-mu[i];
        }
    }
}

  • 筛法求约数个数

用$ d_i \(表示\) i \(的约数个数,\)num_i \(表示\) i $的最小质因子出现次数。

约数个数定理

定理:若$n=\prod_{i=1}^m p_i^{c_i} \(则\)d_i=\prod_{i=1}^m (c_i+1)$。

证明:我们知道$p_i^{c_i} \(的约数有\)p_i0,p_i1,\dots ,p_i^{c_i} \(共\) c_i+1\(个,根据乘法原理,\)n \(的约数个数就是\)\prod_{i=1}^m (c_i+1)$。
实现

因为$ d_i $是积性函数,所以可以使用线性筛。

void sieve(){
    d[1]=1;
    for(int i=2;i<=n;i++){
        if(!v[i]){prime[++cnt]=i;d[i]=2;num[i]=1;}
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
            v[i*prime[j]]=1;
            if(i%prime[j]==0){
                num[i*prime[j]]=num[i]+1;
                d[i*prime[j]]=d[i]/num[i*prime[j]]*(num[i*prime[j]]+1);
                break;
            }
            num[i*prime[j]]=1;
            d[i*prime[j]]=d[i]*2;
        }
    }
}

  • 筛法求约数之和

$f_i \(表示\) i \(的约数和,\)g_i \(表示\) i \(的最小质因子的\) p0+p1+p^2+\dots p^k$。

void sieve(){
    f[1]=g[1]=1;
    for(int i=2;i<=n;i++){
        if(!v[i]){prime[++cnt]=i;g[i]=i+1;f[i]=i+1;}
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
            v[i*prime[j]]=1;
            if(i%prime[j]==0){
                g[i*prime[j]]=g[i]*prime[j]+1;
                f[i*prime[j]]=f[i]/g[i]*g[i*prime[j]];
                break;
            }
            f[i*prime[j]]=f[i]*f[prime[j]];
            g[i*prime[j]]=prime[j]+1;
        }
    }
}

  • 线性筛条件

假如一个 积性函数$ f \(满足:对于任意质数\) p \(和正整数\) k\(,可以在\) O(1) \(时间内计算\) f(p^k)$,那么可以在 $O(n) \(时间内筛出\)f(1),f(2),\dots,f(n) $的值。

设合数\(n\)的质因子分解是\(\prod_{i=1}^k p_i^{\alpha_i}\),其中$ p_1<p_2<\dots<p_k \(为质数,我们在线性筛中记录\)g_n=p_1^{\alpha_1}\(,假如\) n \(被\) x\cdot p \(筛掉(\)p$ 是质数),那么$ g $满足如下递推式:

\[g_n= \begin{cases} g_x\cdot p & x\bmod p=0\\\\ p & \text{otherwise} \end{cases} \]

假如$ n=g_n\(,说明\) n \(就是某个质数的次幂,可以\) O(1) \(计算\) f(n)\(;否则,\)f(n)=f(\frac{n}{g_n})\cdot f(g_n)$。

杜教筛

杜教筛被用来处理数论函数的前缀和问题。对于求解一个前缀和,杜教筛可以在低于线性时间的复杂度内求解。

对于数论函数 f,要求我们计算\(S(n)=\sum_{i=1}^{n}f(i).\)

我们想办法构造一个$ S(n) \(关于\)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) $的递推式

对于任意一个数论函数$ g$,必满足

\[\begin{aligned}\sum_{i=1}^{n}\sum_{d \mid i}g(d)f\left(\frac{i}{d}\right)&=\sum_{i=1}^{n}g(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)\\\iff\sum_{i=1}^{n}(f\ast g)(i)&=\sum_{i=1}^{n}g(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)\end{aligned} \]

证明

\(g(d)f\left(\frac{i}{d}\right)\) 就是对所有$ i\leq n $的做贡献,因此变换枚举顺序,枚举 \(d,\frac{i}{d}\)(分别对应新的$ i,j$)

\[\begin{aligned} &\sum_{i=1}^n\sum_{d \mid i}g(d)f\left(\frac{i}{d}\right)\\ =&\sum_{i=1}^n\sum_{j=1}^{\left\lfloor\frac{n}{i}\right\rfloor}g(i)f(j)\\ =&\sum_{i=1}^ng(i)\sum_{j=1}^{\left\lfloor\frac{n}{i}\right\rfloor}f(j)\\ =&\sum_{i=1}^ng(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) \end{aligned} \]

那么可以得到递推式

\[g(1)S(n)=\sum_{i=1}^n(f\ast g)(i)-\sum_{i=2}^ng(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) \]

那么假如我们可以快速对$\sum_{i=1}^n(f \ast g)(i) $求和,并用数论分块求解 $\sum_{i=2}^ng(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) \(就可以在较短时间内求得\) g(1)S(n)$。

  • 求莫比乌斯函数前缀和

易知,\(\epsilon=\mu*1\),即\(\epsilon(n)=\sum_{d|n}\mu(d)\),故构造\(g=1\),则:

\[S(n)=\sum_{i=1}^n\mu(i)=\sum_{i=1}^n\epsilon(i)-\sum_{i=2}^nS(\lfloor\frac{n}{i}\rfloor)=1-\sum_{i=2}^{n}S(\lfloor\frac{n}{i}\rfloor) \]

观察到$ \lfloor \frac n i \rfloor \(最多只有\) O(\sqrt n) $种取值,我们就可以应用整除分块来计算每一项的值了。

直接计算的时间复杂度为$ O(n^{\frac 3 4})\(。考虑先线性筛预处理出前\) n^{\frac 2 3} \(项,剩余部分的时间复杂度为\)O(\int_{0}{n{\frac 1 3}} \sqrt{\frac{n}{x}} ~ dx)=O(n^{\frac 2 3})$。

对于较大的值,需要用map存下其对应的值,方便以后使用时直接使用之前计算的结果。

  • 求欧拉函数前缀和

易知,\(\text{id}=\varphi*1\),即\(\text{id}(n)=\sum_{d|n}\mu(d)\),故构造\(g=1\),则:

\[S(n)=\sum_{i=1}^n\varphi(i)=\sum_{i=1}^n\text{id}(i)-\sum_{i=2}S(\lfloor\frac{n}{i}\rfloor)=\frac{1}{2}n(n+1)-\sum_{i=2}^nS(\lfloor\frac{n}{i}\rfloor) \]

#include <bits/stdc++.h>
using namespace std;
const int N=2002000,L=2000000;
#define int long long

int prime[N],phi[N],mu[N],v[N];
int cnt,T,n;

map<int,int> mp_phi,mp_mu;

void sieve(){
    phi[1]=mu[1]=1;
    for(int i=2;i<=L;i++){
        if(!v[i]){prime[++cnt]=i;phi[i]=i-1;mu[i]=-1;}
        for(int j=1;j<=cnt&&i*prime[j]<=L;j++){
            v[i*prime[j]]=1;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*phi[prime[j]];
            mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i=1;i<=L;i++) phi[i]+=phi[i-1];
    for(int i=1;i<=L;i++) mu[i]+=mu[i-1];
}

int get_mu(int n){
    if(n<=L) return mu[n];
    if(mp_mu[n]) return mp_mu[n];
    int res=1;
    for(int l=2,r;l<=n;l=r+1){
        r=n/(n/l);
        res-=get_mu(n/l)*(r-l+1);
    }
    return mp_mu[n]=res;
}

int get_phi(int n){
    if(n<=L) return phi[n];
    if(mp_phi[n]) return mp_phi[n];
    int res=n*(n+1)/2;
    for(int l=2,r;l<=n;l=r+1){
        r=n/(n/l);
        res-=get_phi(n/l)*(r-l+1);
    }
    return mp_phi[n]=res;
}

signed main(){
    sieve();
    scanf("%lld",&T);
    while(T--){
        scanf("%lld",&n);
        cout<<get_phi(n)<<' '<<get_mu(n)<<'\n';
    }
    return 0;
}

标签:lfloor,right,frac,筛法,int,sum,left
From: https://www.cnblogs.com/TKXZ133/p/17456582.html

相关文章

  • 筛法--朴素筛法和埃式筛法和线性筛法
    朴素筛法:#include<iostream>#include<algorithm>usingnamespacestd;constintN=1000010;intprimes[N],cnt;boolst[N];voidget_primes(intn){for(inti=2;i<=n;i++){if(!st[i])//st==0代表的是这个数是质数{......
  • 有关素数的基础算法 素性测试 埃氏筛法
    所谓素数,是指恰好有两个约数的正整数。因为n的约数都小于n,所以只需要检查2~ n-1之间所有的整数是否整除n就能判定n是不是素数。如果d是n的约数,那么n/d也是n的约数。由n=d*n/d可知min(d,n/d)  ,所以只需要检查2~ 之间的所有整数就足够了。同理可知,整数分解和约数枚举都......
  • 质数及其筛法
    筛法质数质数,又称素数。如果一个数\(a\in\N^+(a\neq1)\)的因子有且仅有\(1\)和它本身,则称数\(a\)为质数。普通筛法过程枚举\([2,n-1]\),如果\(n\)在这个范围内有因子,则\(n\)不是因数。因为\(n\)的因子成对出现,所以我们可以枚举\([2,\sqrt{n}]\)。Codeboolisprime(in......
  • 初等数学瞎扯Ⅲ:数论函数与筛法
    0.前置知识与基本定义\([op]\):值为\(1\)当且仅当方括号内条件为真。记为艾弗森括号唯一分解定理:一个正整数\(x\)可以被唯一分解为\(\prod\limits_{i=1}^mp_i^{c_i}\),其中\(\foralli\in[1,m],p_i\in\mathbb{P}\)。(关于\(\mathbb{P}\),详见初等数学瞎扯Ⅰ:同余相关)。......
  • python习题-筛法求素数
    【题目描述】用户输入整数n和m(1<n<m<1000),应用筛法求[n,m]范围内的所有素数。【基本思想】用筛法求素数的基本思想是:把从2到N的一组正整数从小到大按顺序排列。从中依次删除2的倍数、3的倍数、5的倍数,直到根号N的倍数为止,剩余的即为2~N之间的所有素数。【源代码程序】defsie......
  • 【ACM算法竞赛日常训练】DAY10题解与分析【月月给华华出题】【华华给月月出题】| 筛法
    DAY10共2题:月月给华华出题华华给月月出题难度较大。......
  • 记录欧式筛法筛选素数
    点击查看代码voidgetPrime(longlongn,vector<int>&prime,vector<bool>&isPrime){isPrime[1]=false;for(inti=2;i<n;++i){if(isPrime[i]){prime.emplace_back(i);}for(constint&a......
  • 欧拉筛法求素数
    在开筛之前,我们要理解一个很好理解的概念,任何一个合数可以拆成一个最小素数和另一个数(可能质数可能合数)的乘积这个最小素数即为这个合数的最小质因子//比如12=2*6,此时2就是12的最小质因子,当然亦有12=3*4,可以看到3也是12的质因子,但不是最小的质因子//而且,对于一合数a=b*q,b为a的最......
  • AcWing 874. 筛法求欧拉函数
    \(AcWing\)\(874.\)筛法求欧拉函数一、题目描述给定一个正整数\(n\),求\(1∼n\)中每个数的欧拉函数之和。输入格式共一行,包含一个整数\(n\)。输出格式共一行,包......
  • 数论基础1(质数判断,分解质因数,筛法,优化筛法,约数,约数个数,约数之和)
    模板://质数判定--试除法//朴素O(N)boolis_prime(intn){ if(n<2)returnfalse; for(inti=2;i<n;i++) { if(n%i==0)returnfalse; } returntrue;}//......