[数论] 欧拉筛法

文章目录

  • 前言
  • 正题
    • 质数
      • 思路
      • 代码
    • 欧拉函数
      • 思路
      • 代码
    • 约数个数
      • 思路
      • 代码
    • 约数和
      • 思路
      • 代码


前言

最近学数论,我是真的绝望,欧拉筛法也只能靠背代码勉强凑合凑合,但在我社CSQ大佬的帮助下,我理解到了其中神奇的奥妙


正题

欧拉筛法是一种可以筛出质数,欧拉函数,约数个数和约数和的筛法
那么我们就对这些问题逐一进行讲解
在这之前,我们先说几个东西:
1、每一个大于等于2的正整数 n n n,都有 n = p 1 w 1 p 2 w 2 … p m w m n=p_1^{w_1}p_2^{w_2}…p_m^{w_m} n=p1w1p2w2pmwm p 1 p_1 p1 p m p_m pm按升序排列)
2、正整数 n n n的欧拉函数 p h i ( n ) = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) … ( 1 − 1 p m ) = p 1 w 1 − 1 ( p 1 − 1 ) ∗ p 2 w 2 − 1 ( p 2 − 1 ) ∗ … ∗ p m w m − 1 ( p m − 1 ) phi(n)=n(1-\frac{1}{p_1})(1-\frac{1}{p_2})…(1-\frac{1}{p_m})=p_1^{w_1-1}(p_1-1)*p_2^{w_2-1}(p_2-1)*…*p_m^{w_m-1}(p_m-1) phi(n)=n(1p11)(1p21)(1pm1)=p1w11(p11)p2w21(p21)pmwm1(pm1)
3、正整数 n n n的约数个数 d ( n ) = ( 1 + w 1 ) ( 1 + w 2 ) … ( 1 + w m ) d(n)=(1+w_1)(1+w_2)…(1+w_m) d(n)=(1+w1)(1+w2)(1+wm)
4、正整数 n n n的约数和 s ( n ) = ( 1 + p 1 + p 1 2 + … + p 1 w 1 ) ( 1 + p 2 + p 2 2 + … + p 2 w 2 ) … … ( 1 + p m + p m 2 + … + p m w m ) s(n)=(1+p_1+p_1^2+…+p_1^{w_1})(1+p_2+p_2^2+…+p_2^{w_2})……(1+p_m+p_m^2+…+p_m^{w_m}) s(n)=(1+p1+p12++p1w1)(1+p2+p22++p2w2)(1+pm+pm2++pmwm)

质数

思路

筛质数是一个相对简单的内容,在普通的筛法中,我们知道当一个数 i i i为质数时,可以筛掉所有 i i i的倍数

但在欧拉筛法中,我们会进行一定的优化,那么外层循环枚举 i i i,判断 i i i是否为质数;内层循环枚举 j j j,这个 j j j实质上就是枚举第 j j j个质数,筛掉 i ∗ p r i m [ j ] i*prim[j] iprim[j],重点就是接下来的步骤:

如果 i % p r i m [ j ] = = 0 i\%prim[j]==0 i%prim[j]==0,我们就要 b r e a k break break掉,这是因为我们筛素数是肯定是希望每一个数都能被它的最小质因子筛掉,当 i % p r i m [ j ] = = 0 i\%prim[j]==0 i%prim[j]==0时,就相当于 i = p r i m [ j ] ∗ k ( k 为 一 个 正 整 数 ) i=prim[j]*k(k为一个正整数) i=prim[j]kk,那么如果我们继续往后枚举到了一个质数 p r i m [ m ] prim[m] prim[m],就要筛去 i ∗ p r i m [ m ] i*prim[m] iprim[m],也就是 p r i m [ j ] ∗ k ∗ p r i m [ m ] prim[j]*k*prim[m] prim[j]kprim[m],因为我们枚举的质数是从小到大的,所以 i ∗ p r i m [ m ] i*prim[m] iprim[m]的最小质因子应该是 p r i m [ j ] prim[j] prim[j],若继续枚举,就会出现冗余的计算

代码

inline void sieve(int x) {
    for(reg int i = 2;i <= x;i ++) {
        if(! vis[i])
            prim[++ len] = i;
        for(reg int j = 1;j <= len && i * prim[j] <= x;j ++) {
            vis[i * prim[j]] = 1;
            if(i % prim[j] == 0)
                break;
        }
    }
}

欧拉函数

思路

我们知道欧拉函数是这样的: p h i ( n ) = p 1 w 1 − 1 ( p 1 − 1 ) ∗ p 2 w 2 − 1 ( p 2 − 1 ) ∗ … ∗ p m w m − 1 ( p m − 1 ) phi(n)=p_1^{w_1-1}(p_1-1)*p_2^{w_2-1}(p_2-1)*…*p_m^{w_m-1}(p_m-1) phi(n)=p1w11(p11)p2w21(p21)pmwm1(pm1)

那么在之前筛质数的基础上,我们知道会进行两种判断,我们也根据这两种情况来求解:

1、 i % p r i m [ j ] = = 0 i\%prim[j]==0 i%prim[j]==0,这意味着 i i i里面包含了至少一个 p r i m [ j ] prim[j] prim[j],所以 i ∗ p r i m [ j ] i*prim[j] iprim[j]也包含了至少一个 p r i m [ j ] prim[j] prim[j],且 i ∗ p r i m [ j ] i*prim[j] iprim[j] i i i的基础上多包含了一个 p r i m [ j ] prim[j] prim[j]

i i i的质因数分解里, p 1 p_1 p1就代表 p r i m [ j ] prim[j] prim[j]

因为 p h i ( i ) = p 1 w 1 − 1 ( p 1 − 1 ) ∗ p 2 w 2 − 1 ( p 2 − 1 ) ∗ … ∗ p m w m − 1 ( p m − 1 ) phi(i)=p_1^{w_1-1}(p_1-1)*p_2^{w_2-1}(p_2-1)*…*p_m^{w_m-1}(p_m-1) phi(i)=p1w11(p11)p2w21(p21)pmwm1(pm1)
所以 p h i ( i ∗ p r i m [ j ] ) = p 1 w 1 − 1 + 1 ( p 1 − 1 ) ∗ p 2 w 2 − 1 ( p 2 − 1 ) ∗ … ∗ p m w m − 1 ( p m − 1 ) phi(i*prim[j])=p_1^{w_1-1+1}(p_1-1)*p_2^{w_2-1}(p_2-1)*…*p_m^{w_m-1}(p_m-1) phi(iprim[j])=p1w11+1(p11)p2w21(p21)pmwm1(pm1)

可得 p h i ( i ∗ p r i m [ j ] ) = p h i ( i ) ∗ p 1 phi(i*prim[j])=phi(i)*p_1 phi(iprim[j])=phi(i)p1,即 p h i ( i ∗ p r i m [ j ] ) = p h i ( i ) ∗ p r i m [ j ] phi(i*prim[j])=phi(i)*prim[j] phi(iprim[j])=phi(i)prim[j]

2、 i % p r i m [ j ] ! = 0 i\%prim[j]!=0 i%prim[j]!=0,就是说 i i i里面没有包含 p r i m [ j ] prim[j] prim[j],所以 i ∗ p r i m [ j ] i*prim[j] iprim[j]里在 i i i的基础上只包含了一个 p r i m [ j ] prim[j] prim[j]

我们把 p r i m [ j ] prim[j] prim[j]记作 p m + 1 p_{m+1} pm+1,指数为1

因为 p h i ( i ) = p 1 w 1 − 1 ( p 1 − 1 ) ∗ p 2 w 2 − 1 ( p 2 − 1 ) ∗ … ∗ p m w m − 1 ( p m − 1 ) phi(i)=p_1^{w_1-1}(p_1-1)*p_2^{w_2-1}(p_2-1)*…*p_m^{w_m-1}(p_m-1) phi(i)=p1w11(p11)p2w21(p21)pmwm1(pm1)
所以 p h i ( i ∗ p r i m [ j ] ) = p 1 w 1 − 1 ( p 1 − 1 ) ∗ p 2 w 2 − 1 ( p 2 − 1 ) ∗ … ∗ p m w m − 1 ( p m − 1 ) ∗ p m + 1 1 − 1 ( p m + 1 − 1 ) phi(i*prim[j])=p_1^{w_1-1}(p_1-1)*p_2^{w_2-1}(p_2-1)*…*p_m^{w_m-1}(p_m-1)*p_{m+1}^{1-1}(p_{m+1}-1) phi(iprim[j])=p1w11(p11)p2w21(p21)pmwm1(pm1)pm+111(pm+11)

可得 p h i ( i ∗ p r i m [ j ] ) = p h i ( i ) ∗ ( p m + 1 − 1 ) phi(i*prim[j])=phi(i)*(p_{m+1}-1) phi(iprim[j])=phi(i)(pm+11),即 p h i ( i ∗ p r i m [ j ] ) = p h i ( i ) ∗ ( p r i m [ j ] − 1 ) phi(i*prim[j])=phi(i)*(prim[j]-1) phi(iprim[j])=phi(i)(prim[j]1)

代码

inline void sieve(int x) {
	phi[1] = 1;
    for(reg int i = 2;i <= x;i ++) {
        if(! vis[i]) {
        	prim[++ len] = i;
        	phi[i] = i - 1;	//因为欧拉函数代表小于这个数的且与这个数互质的数的个数,所以质数的欧拉函数为它本身减1
        }
        for(reg int j = 1;j <= len && i * prim[j] <= x;j ++) {
            vis[i * prim[j]] = 1;
            if(i % prim[j] == 0) {
            	phi[i * prim[j]] = phi[i] * prim[j];
                break;
            }
            phi[i * prim[j]] = phi[i] * (prim[j] - 1);
        }
    }
}

约数个数

思路

先摆公式 d ( n ) = ( 1 + w 1 ) ( 1 + w 2 ) … ( 1 + w m ) d(n)=(1+w_1)(1+w_2)…(1+w_m) d(n)=(1+w1)(1+w2)(1+wm)

和之前的一样,我们分两种情况讨论:

1、 i % p r i m [ j ] = = 0 i\%prim[j]==0 i%prim[j]==0,此时 i i i里面包含了至少一个 p r i m [ j ] prim[j] prim[j] i ∗ p r i m [ j ] i*prim[j] iprim[j]里面也至少包含了一个 p r i m [ j ] prim[j] prim[j]

i i i的质因数分解中,就有 p 1 = p r i m [ j ] p_1=prim[j] p1=prim[j]

因为 d ( i ) = ( 1 + w 1 ) ( 1 + w 2 ) … ( 1 + w m ) d(i)=(1+w_1)(1+w_2)…(1+w_m) d(i)=(1+w1)(1+w2)(1+wm)
所以 d ( i ∗ p r i m [ j ] ) = ( 1 + w 1 + 1 ) ( 1 + w 2 ) … ( 1 + w m ) d(i*prim[j])=(1+w_1+1)(1+w_2)…(1+w_m) d(iprim[j])=(1+w1+1)(1+w2)(1+wm)

可得 d ( i ∗ p r i m [ j ] ) = d ( i ) / ( 1 + p r i m [ j ] ) ∗ ( 2 + p r i m [ j ] ) d(i*prim[j])=d(i)/(1+prim[j])*(2+prim[j]) d(iprim[j])=d(i)/(1+prim[j])(2+prim[j])

但是因为我们在用欧拉筛法求解时,并不知道 w 1 w_1 w1的值,所以我们用一个 s u m sum sum数组来计算并保存这个值, s u m [ i ] sum[i] sum[i]表示 i i i的最小质因数的个数

2、 i % p r i m [ j ] ! = 0 i\%prim[j]!=0 i%prim[j]!=0,此时 i i i里面没有包含 p r i m [ j ] prim[j] prim[j]这个质因子,所以 i ∗ p r i m [ j ] i*prim[j] iprim[j]里面只包含了一个 p r i m [ j ] prim[j] prim[j]

也就是说, i = p 1 w 1 p 2 w 2 … … p m w m i=p_1^{w_1}p_2^{w_2}……p_m^{w_m} i=p1w1p2w2pmwm i ∗ p r i m [ j ] = p 1 w 1 p 2 w 2 … … p m w m p m + 1 w m + 1 i*prim[j]=p_1^{w_1}p_2^{w_2}……p_m^{w_m}p_{m+1}^{w_{m+1}} iprim[j]=p1w1p2w2pmwmpm+1wm+1,而且我们知道 w m + 1 = 1 w_{m+1}=1 wm+1=1

因为 d ( i ) = ( 1 + w 1 ) ( 1 + w 2 ) … ( 1 + w k ) … ( 1 + w m ) d(i)=(1+w_1)(1+w_2)…(1+w_k)…(1+w_m) d(i)=(1+w1)(1+w2)(1+wk)(1+wm)
所以 d ( i ∗ p r i m [ j ] ) = ( 1 + w 1 ) ( 1 + w 2 ) … ( 1 + w k ) … ( 1 + w m ) ( 1 + w m + 1 ) d(i*prim[j])=(1+w_1)(1+w_2)…(1+w_k)…(1+w_m)(1+w_{m+1}) d(iprim[j])=(1+w1)(1+w2)(1+wk)(1+wm)(1+wm+1)

可得 d ( i ∗ p r i m [ j ] ) = d ( i ) ∗ 2 d(i*prim[j])=d(i)*2 d(iprim[j])=d(i)2

代码

inline void sieve(int x) {
    for(reg int i = 2;i <= x;i ++) {
        if(! vis[i]) {
        	prim[++ len] = i;
        	d[i] = 2;	//质数的约数只有1和它本身
        	sum[i] = 1;
        }
        for(reg int j = 1;j <= len && i * prim[j] <= x;j ++) {
            vis[i * prim[j]] = 1;
            if(i % prim[j] == 0) {
            	sum[i * prim[j]] = sum[i] + 1;
            	d[i * prim[j]] = d[i] / (sum[i] + 1) * (sum[i] + 2);
                break;
            }
            sum[i * prim[j]] = 1;
            d[i * prim[j]] = d[i] * 2;
        }
    }
}

约数和

思路

公式在此: s ( n ) = ( 1 + p 1 + p 1 2 + … … + p 1 w 1 ) ( 1 + p 2 + p 2 2 + … … + p 2 w 2 ) … … ( 1 + p m + p m 2 + … … + p m w m ) s(n)=(1+p_1+p_1^2+……+p_1^{w_1})(1+p_2+p_2^2+……+p_2^{w_2})……(1+p_m+p_m^2+……+p_m^{w_m}) s(n)=(1+p1+p12++p1w1)(1+p2+p22++p2w2)(1+pm+pm2++pmwm)

同样,我们分两种情况:

1、 i % p r i m [ j ] = = 0 i\%prim[j]==0 i%prim[j]==0,就是 i i i里面至少包含了一个 p r i m [ j ] prim[j] prim[j],所以 i ∗ p r i m [ j ] i*prim[j] iprim[j]里面也包含了至少一个 p r i m [ j ] prim[j] prim[j],而且在 i ∗ p r i m [ j ] i*prim[j] iprim[j]里面的 p r i m [ j ] prim[j] prim[j] i i i多了一个

i i i的分解中, p r i m [ j ] prim[j] prim[j]实质上就是 p 1 p_1 p1,则
i = p 1 w 1 p 2 w 2 … p m w m i=p_1^{w_1}p_2^{w_2}…p_m^{w_m} i=p1w1p2w2pmwm i ∗ p r i m [ j ] = p 1 w 1 + 1 p 2 w 2 … p k w k + 1 … p m w m i*prim[j]=p_1^{w_1+1}p_2^{w_2}…p_k^{w_k+1}…p_m^{w_m} iprim[j]=p1w1+1p2w2pkwk+1pmwm

因为 s ( i ) = ( 1 + p 1 + p 1 2 + … … + p 1 w 1 ) ( 1 + p 2 + p 2 2 + … … + p 2 w 2 ) … ( 1 + p m + p m 2 + … … + p m w m ) s(i)=(1+p_1+p_1^2+……+p_1^{w_1})(1+p_2+p_2^2+……+p_2^{w_2})…(1+p_m+p_m^2+……+p_m^{w_m}) s(i)=(1+p1+p12++p1w1)(1+p2+p22++p2w2)(1+pm+pm2++pmwm)
所以 s ( i ∗ p r i m [ j ] ) = ( 1 + p 1 + p 1 2 + … … + p 1 w 1 + p 1 w 1 + 1 ) ( 1 + p 2 + p 2 2 + … … + p 2 w 2 ) … ( 1 + p m + p m 2 + … … + p m w m ) s(i*prim[j])=(1+p_1+p_1^2+……+p_1^{w_1}+p_1^{w_1+1})(1+p_2+p_2^2+……+p_2^{w_2})…(1+p_m+p_m^2+……+p_m^{w_m}) s(iprim[j])=(1+p1+p12++p1w1+p1w1+1)(1+p2+p22++p2w2)(1+pm+pm2++pmwm)

此时我们用一个 p s u m psum psum数组,其中 p s u m [ i ] psum[i] psum[i]表示 i i i的最小质因子 p p p的一个形如 1 + p + p 2 + … … + p w 1+p+p^2+……+p^w 1+p+p2++pw的等比数列的值
那么 p s u m [ i ∗ p r i m [ j ] ] = p s u m [ i ] ∗ p r i m [ j ] + 1 psum[i*prim[j]] = psum[i]*prim[j]+1 psum[iprim[j]]=psum[i]prim[j]+1 s ( i ∗ p r i m [ j ] ) = s ( i ) / p s u m [ i ] ∗ p s u m [ i ∗ p r i m [ j ] ] s(i*prim[j])=s(i)/psum[i]*psum[i*prim[j]] s(iprim[j])=s(i)/psum[i]psum[iprim[j]]

2、 i % p r i m [ j ] ! = 0 i\%prim[j]!=0 i%prim[j]!=0,此时 i i i里面没有包含 p r i m [ j ] prim[j] prim[j],那么 i ∗ p r i m [ j ] i*prim[j] iprim[j]里面就只包含了一个 p r i m [ j ] prim[j] prim[j]

假设 i = p 1 w 1 p 2 w 2 … p m w m i=p_1^{w_1}p_2^{w_2}…p_m^{w_m} i=p1w1p2w2pmwm,那么 i ∗ p r i m [ j ] = p 1 w 1 p 2 w 2 … p m w m p m + 1 w m + 1 i*prim[j]=p_1^{w_1}p_2^{w_2}…p_m^{w_m}p_{m+1}^{w_{m+1}} iprim[j]=p1w1p2w2pmwmpm+1wm+1

因为 s ( i ) = ( 1 + p 1 + p 1 2 + … … + p 1 w 1 ) ( 1 + p 2 + p 2 2 + … … + p 2 w 2 ) … ( 1 + p m + p m 2 + … … + p m w m ) s(i)=(1+p_1+p_1^2+……+p_1^{w_1})(1+p_2+p_2^2+……+p_2^{w_2})…(1+p_m+p_m^2+……+p_m^{w_m}) s(i)=(1+p1+p12++p1w1)(1+p2+p22++p2w2)(1+pm+pm2++pmwm)
所以 s ( i ∗ p r i m [ j ] ) = ( 1 + p 1 + p 1 2 + … … + p 1 w 1 ) ( 1 + p 2 + p 2 2 + … … + p 2 w 2 ) … ( 1 + p m + p m 2 + … … + p m w m ) ( 1 + p m + 1 ) s(i*prim[j])=(1+p_1+p_1^2+……+p_1^{w_1})(1+p_2+p_2^2+……+p_2^{w_2})…(1+p_m+p_m^2+……+p_m^{w_m})(1+p_{m+1}) s(iprim[j])=(1+p1+p12++p1w1)(1+p2+p22++p2w2)(1+pm+pm2++pmwm)(1+pm+1)

可得 s ( i ∗ p r i m [ j ] ) = s ( i ) ∗ ( 1 + p r i m [ j ] ) s(i*prim[j])=s(i)*(1+prim[j]) s(iprim[j])=s(i)(1+prim[j])

代码

inline void sieve(int x) {
    for(reg int i = 2;i <= x;i ++) {
        if(! vis[i]) {
        	prim[++ len] = i;
        	psum[i] = s[i] = i + 1;
        }
        for(reg int j = 1;j <= len && i * prim[j] <= x;j ++) {
            vis[i * prim[j]] = 1;
            if(i % prim[j] == 0) {
            	psum[i * prim[j]] = psum[i] * prim[j] + 1;
            	s[i * prim[j]] = s[i] / psum[i] * psum[i * prim[j]]
                break;
            }
            psum[i * prim[j]] = prim[j] + 1;
            s[i * prim[j]] = s[i] * psum[i * prim[j]];
        }
    }
}

你可能感兴趣的:(数论)