约数定理+线性筛素数

约数定理

已知,令d(x)表示x的因子(或者叫约数)个数则

.

线性筛法打d(x)表:每个数都是被它最小的素因子筛掉的,那么用g[i]表示i的最小素因子的幂次,注意d(x)的表达式,它是部分积性函数d(p1^a1*p2^a2)=d(p1^a1)*d(p2^a2)

void init(){
    memset(vis,false,sizeof vis);
    d[1]=1, g[1]=1;//1的约数个数是1,1的最小素因子的幂次也记为1
    for(int i=2;i<N;i++) {//从2开始一直到N逐个数扫一次
        if(!vis[i]) {
            pri[tot++]=i;//如果扫到i时未被访问则它就是素数
            d[i]=2;//素数的约数有两个
            g[i]=1;//质数的最小素因子就是他本身,幂次为1
        }
        for(int j=0;j<tot && i*pri[j]<N;j++){//穷举已出现的质数
            vis[i*pri[j]]=true;//此数已被访问不是质数(注意一个合数可能会被多次访问)
            if(i%pri[j]) {//pri[j]不是i的素因子
                d[i*pri[j]]=d[i]*2;//部分积性(不一定最小,但最后一次更新必定最小)
                g[i*pri[j]]=1;//pri[j]是i*pri[j]的素因子,只出现了1次(后文解释最小)
            }
            else {// pri[j]是i的素因子
                d[i*pri[j]]=d[i]/(g[i]+1)*(g[i]+2);//除掉原幂次,乘上新幂次(后文解释最小)
                g[i*pri[j]]=g[i]+1;//多一个pri[j]素因子,所以i的最小素因子个数g[i]要加1 
                break;
            }
        }
    }
}

特别指出,此处不同下面筛素数,遇到第一个素因子后跳出,换句话说,上面的模板中一个合数会访问很多次,而且每次都会对D,G进行刷新,而最后一次刷新i*pri[j]的就是最小pri[j]乘上一个最大的i,所以只需关注最后一次更新就可以

关于最后一次更新的最小pri[j],首先i*pri[j]是i的倍数,所以i的因子一定是i*pri[j]的因子,则i*pri[j]最小的因子,也一定是i的最小因子,也就是说i的最小因子有g[i]个,而i*pri[j]的最小因子有g[i]+1个,因为他们有相同的最小因子

而且可以放心的就是在用数i去更新i*pri[j]时,i的d,g数组都肯定是已经更新完成的(如果是质数,在上面判质数时更新,如果是合数,也会在最后一次正确更新

举例:如12=2^2 * 3,第一次更新时是i=4时,扫到质数3此时3不是4的素因子,就是设D12为D4的两倍,G1=1,显然是不对的,但是在第二次更新时i=6,扫到质数2此时2是6的素因子,于是设D12=D6/(G6+1)*(G6+2)=4/2*3=6,G12=G6+1=1+1=2,正确。又如18=2 * 3^2同理,第一次更新时是i=6,扫到质数3不对,第二次i=9扫到质数2时就正确了

关于d其实很易理解,如果是新的质数,就会乘多一个Ak^Pk,此时Pk为1故乘2,而如果是合数,原来的d[i]这个积性函数值是有g[i]个pri[j]的,就是说含有pri[j]^(g[i]+1)这一项乘数,现在求i*pri[j]又多了

线性筛素数 

for(ll  i = 2 ; i < N ; i++){
if(! isNotPrime[i])prime[num_prime ++]=i;
for(ll  j = 0 ; j < num_prime && i * prime[j] <  N ; j ++){ 
isNotPrime[i * prime[j]] = 1;  //每个质数的i倍都是合数
if( !(i % prime[j] ) ) break;    //遇到i最小的素数因子就可以停止(下文解释)
//一句话证明:令I为X,prime[j]为Y,由i % prime[j]==0知X=KY
//KY*(Y+1)= K(Y+1)*Y,显然K(Y+1)>KY,(Y+1)>Y
//例子i = 8; 由于i%2 == 0;可以跳出,因为8*3=(4*2)*(2+1)= (4*(2+1))*(2)=12*2
//同理KY*(Y+2)= K(Y+2)*Y,显然K(Y+2)>KY,(Y+2)>Y,8*4=16*2
}
}   

思想证明:理解代码中 if(i%primes[j] == 0)break;的关键

当前数字i是素数,易知一个大的素数 i 乘以不大于 i 的素数,这样筛除的数跟之前的是不会重复的。筛出的数都是 N=p1*p2的形式, p1,p2之间不相等
    当前数字i是合数,则i=p1^a * p2^b * p3^c(p1<p2<p3且均为素数),一次循环筛除小于等于p1的素数乘以i得到的数。比如p1之前有pi,pj和pk三个素数,则此次循环筛掉pi*i,pj*i,pk*i和p1*i,实现见代码的关键一,prime 里的素数都是升序排列的,break时的遇到能整除的第一个prime[j] 就是这里的p1


    从图上我们看到,第一列筛掉的是最小素因子是2的数,第二列筛掉的是最小素因子为3的数,依次类推,可以把所有的合数都筛掉,因为是按照最小素因子筛选,所以可以保证每个数都只会被筛一遍

         说得通俗点,数i跟素数p相乘,其中i被p整除,如果这个如果存在一个更小的素数q能整除i则原来的式子i*p,i可以分出一个q,然后剩余部分r乘上p,式子变为q*r,则q<p,r>i,这就解释了为何i%primes[j] == 0就可以跳出

例如: 如果i = 8; 由于i%2 == 0; 因此对于i=8就只需检查primes[1]=2即可,因为对于大于primes[1]的质数,如3有8*3=2*4*3=12*2,也就是24(8*3=24)并不需要在8时检查,在12时才检查

猜你喜欢

转载自blog.csdn.net/cj1064789374/article/details/85390012