线性筛求约数个数

写欧拉计划第十二题的时候,求约数个数,暴力实在太慢,顺便写篇博客记录一下高效方法

改编自: https://blog.csdn.net/ControlBear/article/details/77527115

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

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

prim[i] 表示 第 i 个素数

素数

根据基本算数定理,每一个大于等于2的正整数,都可以被分解成
N = p 1 a 1 p 2 a 2 . . . p n a n N = p_1^{a_1}p_2^{a_2}...p_n^{a_n} N=p1a1p2a2...pnan
其中,p为素数。

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

mark = [0] * N
prim = [0] * N
def initial():
    cnt = 0
    for i in range(2, N):
        if not mark[i]:
            prim[cnt] = i
            cnt += 1
        for j in range(0, cnt):
            if i * prim[j] < N:
                mark[i * prim[j]] = 1
                if not i % prim[j]:
                    break

其中,mark,为已知质数的倍数。

## 约数

算数基本定理中,根据拆分后的素因子的指数,我们可以求出每个N的约数的个数
d ( N ) = ( 1 + a 1 ) ( 1 + a 2 ) . . . ( 1 + a n ) d(N) = (1+a_1)(1+a_2)...(1+a_n) d(N)=(1+a1)(1+a2)...(1+an)
根据这个式子,可以用线性筛去筛去当前N的约数个数。

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

①当前数是素数

当前 d ( N ) = ( 1 + 1 ) = 2 d(N)=(1+1)=2 d(N)=(1+1)=2, 因为素数只有一个素因子(它本身),并且指数为1,

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

i % p r i m [ j ] ! = 0 i \% prim[j] !=0 i%prim[j]!=0

i中不包含 prim[j]这个素因子,然而在i*prim[j]中,包含了一个prim[j]

可以从前面得到i的所有约数个数 ( 1 + a 1 ) ( 1 + a 2 ) . . . ( 1 + a n ) (1+a_1)(1+a_2)...(1+a_n) (1+a1)(1+a2)...(1+an)

然后补上 prim[j]的个数 ( 1 + a 1 ) ( a + a 2 ) . . . ( 1 + a n ) ∗ ( 1 + 1 ) (1+a_1)(a+a_2)...(1+a_n)*(1+1) (1+a1)(a+a2)...(1+an)(1+1)

所以最后得到 d ( i ∗ p r i m ( j ) ) = d ( i ) ∗ d ( p r i m [ j ] ) d(i*prim(j))=d(i)*d(prim[j]) d(iprim(j))=d(i)d(prim[j])

而且由于当前的 prim[j] 必然是 i * prim[j]的最小素因子,要记录下这个最小素因子个数

所以 num[i * prim[j]] = 1

③i % prim[j] == 0

i中必然包含了至少一个prim[j], 而且prim[j]也必然是i的最小素因子。

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

那么 i * prim[j]的约数个数应该是 ( 1 + a 1 + 1 ) ( 1 + a 2 ) . . . ( 1 + a n ) (1+a_1+1)(1+a_2)...(1+a_n) (1+a1+1)(1+a2)...(1+an)

之后,i的最小素因子个数为num[i], 而d(i)中已经包含了 ( 1 + a 1 ) ( 1 + a 2 ) . . . ( 1 + a n ) (1+a_1)(1+a_2)...(1+a_n) (1+a1)(1+a2)...(1+an)

这时可以除去第一项 1 + a 1 1+a_1 1+a1,然后乘以 1 + a 1 + 1 1+a_1+1 1+a1+1,就可得到d(i * prim[j])的约数的个数

d ( i ∗ p r i m [ j ] ) = d ( i ) / ( n u m [ i ] + 1 ) ∗ ( n u m [ i ] + 2 ) d(i * prim[j]) = d(i) / (num[i]+1) * (num[i]+2) d(iprim[j])=d(i)/(num[i]+1)(num[i]+2)

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

N = 20
d = [0] * N
prim = [0] * N
num = [0] * N
mark = [0] * N
def initial():
    cnt = 0
    d[1] = 1
    for i in range(2, N):
        if not mark[i]:
            prim[cnt] = i
            cnt += 1
            num[i] = 1
            d[i] = 2
        for j in range(0, cnt):
            if i * prim[j] < N:
                mark[i * prim[j]] = 1
                if not i % prim[j]:
                    num[i * prim[j]] = num[i] + 1
                    d[i * prim[j]] = d[i] / (num[i] + 1) * (num[i*prim[j]]+ 1)
                    break
                d[i * prim[j]] = d[i] * d[prim[j]]
                num[i * prim[j]] = 1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

involute__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值