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

14 篇文章 0 订阅
1 篇文章 0 订阅

转自 http://blog.csdn.net/tc_to_top/article/details/48025849

线性筛

线性筛在数论中起着至关重要的作用,可以大大降低求解一些问题的时间复杂度,使用线性筛有个前提(除了素数筛)所求函数必须是数论上定义的积性函数,即对于正整数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 = p1^d1 + p2^d2 + … + pk^dk,其中pi为素数
则n的正约数的个数就是 :facnum[n] = (1 + d1) * (1 + d2) * … * (1 + dk)
我们需要一个辅助数组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也费不少

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值