数论线性筛总结 (素数筛,欧拉函数筛,莫比乌斯函数筛,前n个数的约数个数筛)

线性筛

线性筛在数论中起着至关重要的作用,可以大大降低求解一些问题的时间复杂度,使用线性筛有个前提(除了素数筛)所求函数必须是数论上定义的积性函数,即对于正整数n的一个算术函数 f(n),若f(1)=1,且当a,b互质时f(ab)=f(a)f(b),在数论上就称它为积性函数,若a,b不互质也满足的话则称作完全积性函数,下面说明每个筛子是怎么筛的。

最基础的是素数筛,其它三个筛都是以素数筛为前提

素数筛

void get_prime()
{
    int pnum = 0;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
            p[pnum ++] = i;
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)
                break;
        }
    }
}

主要是break那里,比如12这个数,在普通筛的时候12要被2和3都筛一次,显然这种多余的操作会增加时间复杂度,线性筛中一个数字只被它最小的素因子筛掉,12只被2筛掉,当i等于6的时候2*6==12筛掉12,这时候6%2==0可以break了,如果不break,那么6还会把18筛掉,此时是通过6*3来筛掉18,可是显然18最小的素因子是2,所以当i枚举到9的时候有9*2==18,这样18就又被筛了一次,因此在i等于6的时候不用拿6去筛18,下面用公式来说明:

当p[j]是i的因子时,设i=p[j]*k,因为素因子从小到大枚举,所以p[j]是i的最小素因子,此时i已经无需再去剔除p[j‘]*i (j‘>j) 形式的合数了,因为p[j‘]*i可以写成p[j‘]*(p[j]*k)=p[j]*(p[j‘]*k),也就是说所有的p[j‘]*i将会被将来的某个i‘=p[j‘]*k剔除掉,当前的i已经不需要了。

欧拉函数筛

欧拉函数phi[i]表示的是与1-i中与i互质的数的个数。

求解时分三种情况:

1.当i为素数时,显然phi[i] = i - 1

2.当i % p[j] != 0时,gcd(i, p[j]) = 1,由积性函数的性质可得phi[i * p[j]] = phi[i] * phi[p[j]] = phi[i] * (p[j] - 1)  (p数组表示素数)

3.当i % p[j] ==0时,根据欧拉函数的求法:phi[n] = n * ∏(1 - 1/p),p为n的质因子,故若i % p[j] == 0,i * p[j]的质因子数不变

则phi[i * p[j]] = i * p[j] * ∏(1 - 1/p) = p[j] * i * ∏(1 - 1/p) = p[j] * phi[i]

由此得到欧拉函数筛:

void get_eular()
{
    pnum = 0;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
        {
            p[pnum ++] = i;
            phi[i] = i - 1;
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)
            {
                phi[i * p[j]] = phi[i] * p[j];
                break;
            }
            phi[i * p[j]] = phi[i] * (p[j] - 1);
        }
    }
} 

莫比乌斯函数筛

莫比乌斯函数mob[i]

若i为奇数个不同素数之积mob[i] = -1

若i为偶数个不同素数之积mob[i] = 1

若i有平方因子则mob[i] = 0。

这个做起来比欧拉函数容易,在素数筛上,若i为素数则mob[i] = -1,若i % p[j] == 0,则mob[i * p[j]] = 0,显然p[j]就是它的平方因子,否则mob[i * p[j]] = -mob[i]

由此得到莫比乌斯函数筛:

void Mobius()
{
    int pnum = 0;
    mob[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(noprime[i])
        {
            p[pnum ++] = i;
            mob[i] = -1;
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = false;
            if(i % p[j] == 0)
            {
                mob[i * p[j]] = 0;
                break;
            }
            mob[i * p[j]] = -mob[i];
        }
    }
}

前n个数的约数个数筛

facnum[i]表示i的约数个数

通过素数筛得到前n个数的约数个数非常巧妙,首先根据约数个数定理:

对于一个大于1正整数n可以分解质因数:

则n的正约数的个数就是

:

我们需要一个辅助数组d[i],表示i的最小质因子的次幂,(最小的原因是素数筛里每次都是用最小的质因子来筛合数的),还是三种情况:

1.当i为素数时,facnum[i] = 2;d[i] = 1,很好理解

2.当i % p[j] != 0时,gcd(i, p[j]) =1,由积性函数的性质可得facnum[i * p[j]] = facnum[i] * facnum[p[j]] = facnum[i] * 2

d[i * p[j]] = 1(无平方因子)

3.当i % p[j] == 0时,出现平方因子,最小质因子的次幂加1,因此有facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2)

d[i * p[j]] = d[i] + 1

由此得到前n个数的约数个数筛:

void get_facnum()
{
    int pnum = 0;
    facnum[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
        {
            p[pnum ++] = i;
            facnum[i] = 2;
            d[i] = 1;
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)
            {
                facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2);
                d[i * p[j]] = d[i] + 1;
                break;
            }
            facnum[i * p[j]] = facnum[i] * 2;
            d[i * p[j]] = 1;
        }
    }
}

四合一

void get_all()
{
    int pnum = 0;
    phi[1] = 1;
    mob[1] = 1;
    facnum[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(!noprime[i])
        {
            phi[i] = i - 1;
            mob[i] = -1;
            p[pnum ++] = i;
            facnum[i] = 2;
            d[i] = 1;
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            noprime[i * p[j]] = true;
            if(i % p[j] == 0)
            {
                phi[i * p[j]] = phi[i] * p[j];
                mob[i * p[j]] = 0;
                facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2);
                d[i * p[j]] = d[i] + 1;
                break;
            }
            phi[i * p[j]] = phi[i] * (p[j] - 1);
            mob[i * p[j]] = -mob[i];
            facnum[i * p[j]] = facnum[i] * 2;
            d[i * p[j]] = 1;
        }
    }
}

最后吐槽一下,对于素数筛里的判断函数,最好用noprime,因为全局默认值为false,有的时候MAX为1e7之类的,memset成true也费不少

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-13 06:35:54

数论线性筛总结 (素数筛,欧拉函数筛,莫比乌斯函数筛,前n个数的约数个数筛)的相关文章

线性筛素数(欧拉筛)

线性筛素数(欧拉筛) 欧拉筛为啥是\(O(n)\)的呢?我们先来看看代码. #include <cstdio> using namespace std; const int maxn=10000000; int n, m, prime[maxn], isnt_prime[maxn], tot; void get_prime(int n){ isnt_prime[0]=isnt_prime[1]=1; for (int i=2; i<=n; ++i){ //当前数是所有数小于n的数而不只是

数论学习之费马与欧拉

数论复习之费马与欧拉 QB_UDG  2016年11月8日10:16:18 1.费马小定理 Fermat Theory 如果 p是素数,且a与p互质,即gcd(a,p)=1   那么(a^p-1) ≡ 1 (mod p) 应用: 求乘法逆元   乘法逆元: (x*x')≡ 1 (mod p) 称x'为x模p的乘法逆元 (注意,一定要是余1) 逆元 :(b/a) (mod n) = (b * x) (mod n). x表示a的逆元.并且 a*x ≡ 1 (mod n)  注意:只有当a与n互质的时

初等数论-Base-1(筛法求素数,欧拉函数,欧几里得算法)

前言 初等数论在OI中应用的基础部分,同机房的AuSquare和zhou2003君早就写完了,一直划水偷懒的Hk-pls表示很方,这才开始了这篇博客. $P.S.$可能会分部分发表. 筛法求素数 埃式筛素数 问题:求$[1,n]$中的所有素数 总体思路就是在$[2,n]$中每当我们找到一个新的素数,在把它加入我们的素数队列的同时我们把它的倍数全部打上标记(包括它自己),下一个没有被标记的数就是新的素数. void find_prime(int n){ memset(used,0,sizeof(u

线性筛素数、欧拉函数

判断一个数n是否是素数,众所周知可以用O(sqrt(n))的方法. 但是如果要求很多个数,这个方法就不太好了.(比如所有小于n的数,复杂度就是O(n1.5).) 埃拉托斯特尼筛法,大家都听说过.从2到n,去掉每个数的倍数,剩下来的就是质数. 不过这个方法会重复删除,比如6是2.3的倍数,会被删2次,因子越多,删的次数就越多. 改进之后的线性筛保证每个数只被最小的质因子删,所以是O(n)的. #include<cstdio> #include<cstring> #define MAX

欧拉筛素数+求欧拉函数

线性筛法 prime记录素数,num_prime素数下标 它们保证每个合数只会被它的最小质因数筛去 a[0]=a[1]=1; for(int i=2;i<=n;i++) { if(!a[i]) prime[num_prime++]=i; for(int j=0;j<num_prime&&i*prime[j]<=n;j++) { a[i*prime[j]]=1; if(!(i%prime[j])) break; } } } 欧拉函数 是 积性函数:对于任意互质的整数a和b有

【51nod-1239&amp;1244】欧拉函数之和&amp;莫比乌斯函数之和 杜教筛

题目链接: 1239:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1239 1244:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1244 杜教筛裸题,不过现在我也只会筛这俩前缀和... $$s(n)=\sum _{i=1}^{n}f(i)$$ 那么就有: $$\sum_{i=1}^{n}f(i)\lfloor \frac{n}{i} \

【bzoj3512】DZY Loves Math IV 杜教筛+记忆化搜索+欧拉函数

Description 给定n,m,求\(\sum_{i=1}^{n}\sum_{j=1}^{m}\varphi(ij)\)模10^9+7的值. Input 仅一行,两个整数n,m. Output 仅一行答案. Sample Input 100000 1000000000 Sample Output 857275582 数据规模 1<=n<=10^5,1<=m<=10^9. sol %%%ranwen!!! 前置技能: \(n=\sum_{d|n}\varphi(d)\) \(\v

欧拉线性筛 和 欧拉函数的求值

PS:求逆元的部分在文章最后...最好也看看前边的知识吧qwq 用筛法求素数的基本思想是:把从1开始的.某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉.剩下的数中选择最小的数是素数,然后去掉它的倍数.依次类推,直到筛子为空时结束.(来自 百度百科) 一般的筛法(埃拉托斯特尼筛法)的效率是O(nlglgn),但出题人卡你可就凉了.. (就不介绍了(逃)) 下面我们来说O(n)的欧拉线性筛 埃筛之所以慢,是因为有些合数被重复筛除(如:6会被2和3重复筛) 但是欧拉筛保证 每一个数p,

欧拉筛(线性筛)

素数筛,就是按照顺序把合数踢掉,剩下的是素数. 欧拉筛是一种O(n)求素数的筛法.他避免了埃拉特斯特尼筛法对同一数的多次筛除. 欧拉筛的原理是只通过数的最小质因数筛数. 先上代码: #include <cstdio> using namespace std; const int maxn=10000000; int n, m, prime[maxn], isnt_prime[maxn], tot; void get_prime(int n){ isnt_prime[0]=isnt_prime[