转自 https://blog.csdn.net/controlbear/article/details/77527115

刚才手动推了一下 用线性筛筛约数个数和约数和,就顺便写篇博客记录一下。不过网上应该也有不少人推过了。


根据算术基本定理我们可以知道,每一个大于等于2的正整数,都可以被分解成这种形式。

,其中 p 为素数。

线性筛就是每一次被最小素因子给筛出。


线性筛写法 (只筛素数) [我个人比较常用]

 
  1. const int N= 1e5+ 5;
  2. bool mark[N];
  3. int prim[N];
  4. int cnt;
  5. void initial()
  6. {
  7. cnt= 0;
  8. for ( int i= 2 ; i<N ; ++i)
  9. {
  10. if (!mark[i])
  11. prim[cnt++]=i;
  12. for ( int j= 0 ; j<cnt && i*prim[j]<N ; ++j)
  13. {
  14. mark[i*prim[j]]= 1;
  15. if (!(i%prim[j]))
  16. break;
  17. }
  18. }
  19. }


这是我们比较常见的线性筛,当然,我们也会常用来筛欧拉函数跟莫比乌斯函数,因为积性函数基本上都可以用线性筛筛出。

这里就不写出来了。T_T 大家应该都筛过吧(我懒得贴了)


约数个数

算术基本定理中,根据拆分后的素因子的指数,我们可以求出每个 N 的约数的个数。


根据这个式子,我们可以用线性筛去筛出当前 N 的约数个数。

筛的过程中,我们需要保存下最小素因子的个数。


下面证明中 

d(i) 表示 i 的约数个数  

num[i] 表示 i 的最小素因子的个数 

prim[i] 表示 第 i 个素数


① 当前数是素数

这种情况我们很容易得到,当前的 d(N) = (1+1) = 2

因为素数只有一个素因子(就是它本身),并且指数为 1 。

而最小素因子个数 num[N] = 1


② i%prim[j] !=0

这种情况,我们可以知道 i 当中,并不包含 prim[j] 这个素因子,然而,i*prim[j] 中, 包含了一个 prim[j]

我们可以从前面得到 i 的所有约数个数   

然后在补上 当前多了 素因子 prim[j] 的个数

所以最后 d(i*prim[j]) = d(i)*d(prim[j])

而且由于 当前的 prim[j]  必然是 i*prim[j] 的最小素因子 (因为从小到大枚举啊!), 我们要记录下这个最小素因子的个数

所以保存一个个数 num[i*prim[j]] = 1


③ i%prim[j]==0

这种情况, i 中必然包含了至少 1 个 prim[j] ,而且 prim[j] 也必定是 i 的最小素因子,因为每次枚举都是从小的素数开始枚举。

而 i*prim[j] 比起 i 则是多了一个最小素因子个数,即 

那么 i*prim[j] 的约数个数 应该是 

之后,我们就要用到我们之前记录下的最小素因子个数了,因为我们可以知道 i 的最小素因子个数 为 num[i] ,而 d(i) 中 已经包含了

这时我们 我们可以除去第一项  然后乘以   ,就可以得到 d(i*prim[j]) 的约数个数了。

d(i*prim[j]) = d(i) / (num[i]+1) * (num[i]+2)

当前 num[i*prim[j]] = num[i]+1 ,继续保存下当前最小素因子个数。


根据这样,我们就能用线性筛打表出 1 到 N 的数的约数个数。

 
  1. const int N= 1e5+ 5;
  2. bool mark[N];
  3. int prim[N],d[N],num[N];
  4. int cnt;
  5. void initial()
  6. {
  7. cnt= 0;
  8. d[ 1]= 1;
  9. for ( int i= 2 ; i<N ; ++i)
  10. {
  11. if (!mark[i])
  12. {
  13. prim[cnt++]=i;
  14. num[i]= 1;
  15. d[i]= 2;
  16. }
  17. for ( int j= 0 ; j<cnt && i*prim[j]<N ; ++j)
  18. {
  19. mark[i*prim[j]]= 1;
  20. if (!(i%prim[j]))
  21. {
  22. num[i*prim[j]]=num[i]+ 1;
  23. d[i*prim[j]]=d[i]/(num[i]+ 1)*(num[i*prim[j]]+ 1);
  24. break;
  25. }
  26. d[i*prim[j]]=d[i]*d[prim[j]];
  27. num[i*prim[j]]= 1;
  28. }
  29. }
  30. }




约数和

[其实证明跟约数个数基本一样,只不过是用的式子有点不一样]

算数基本定理中,如果要求N的约数的和,可以用这条式子。



同样,我们一样可以用这条式子去筛出当前 N 的约数和。


筛的过程中,我们需要保存最小素因子的那一项的和,即


下面证明中

sd(i) 表示 i 的约数和

sp[i] 表示 i 的最小素因子的等比数列的和 (我不知道怎么形容这个啊,就上面说要保存的那一项)

prim[i] 表示第 i 个素数


①当前数是素数

这种情况我们可以很容易得到,当前的 sd(N) = 1+i

因为素数只有一个素因子,即只有一项合式,且指数最高为 1 。

而该项 sp[N] = 1+i


② i%prim[j] !=0

这种情况,我们可以知道 i 当中,并不包含 prim[j] 这个素因子,然而,i*prim[j] 中, 包含了一个 prim[j]

而前面我们已经得到了 i 的约数和了,而其中不包括 (1+prim[j]) 这一项,而 i*prim[j] 的约数和只是多了这一项。

i 的约数和是

那么 i*prim[j] 的最后结果应该是


sd(i*prim[j]) = sd(i) * sd(prim[j])

而 prim[j] 是 i*prim[j] 的最小素因子 (因为从小到大枚举啊! [第二次提了]),因此sp[i*prim[j]] = 1+prim[j] 


③ i%prim[j]==0

这种情况, i 中必然包含了至少 1 个 prim[j] ,而且 prim[j] 也必定是 i 的最小素因子,因为每次枚举都是从小的素数开始枚举。

只不过 i*prim[j] 比起 i 则是在最小素因子那一项多了

那么 i*prim[j] 的约数和就是

之后,我们用到前面保存下来的最小素因子那一项的和,因为 i 中 应该是


我们可以知道最小素因子的那一项的和,而要得到

只需要将第一项 乘以 prim[j] 然后再 加一,就可以得到了。

sd(i*prim[j]) = sd(i) / sp[i] * (sp[i]*prim[j]+1)

当前 sp[i*prim[j]] = sp[i]*prim[j]+1,继续保存最小素因子一项的和。


根据这样,我们就可以能用线性筛打表出 1 到 N 的约数和。

 
  1. const int N= 1e5+ 5;
  2. bool mark[N];
  3. int prim[N];
  4. long long sd[N],sp[N];
  5. int cnt;
  6. void initial()
  7. {
  8. cnt= 0;
  9. sd[ 1]= 1;
  10. for ( int i= 2 ; i<N ; ++i)
  11. {
  12. if (!mark[i])
  13. {
  14. prim[cnt++]=i;
  15. sd[i]=i+ 1;
  16. sp[i]=i+ 1;
  17. }
  18. for ( int j= 0 ; j<cnt && i*prim[j]<N ; ++j)
  19. {
  20. mark[i*prim[j]]= 1;
  21. if (!(i%prim[j]))
  22. {
  23. sp[i*prim[j]]=sp[i]*prim[j]+ 1;
  24. sd[i*prim[j]]=sd[i]/sp[i]*sp[i*prim[j]];
  25. break;
  26. }
  27. sd[i*prim[j]]=sd[i]*sd[prim[j]];
  28. sp[i*prim[j]]= 1+prim[j];
  29. }
  30. }
  31. }