天天看點

線性篩求 約數個數 與 約數和

線性篩求 約數個數 與 約數和

線性篩,顧名思義,就是歐拉篩,線上性時間内可以求出答案,也就是\(O(N)\)的時間,非常牛\(X\)的效率。

一、約數個數

根據數字唯一分解定理,設

\[\LARGE n=p_1^{r_1}*p_2^{r_2}*p_3^{r_3}*...*p_k^{r_k}

\]

對于每個\(n\)的約數,一定是由以上質因數\(p_1 \sim p_k\)相乘而來,根據乘法原理,每個質因數都可以選擇\(0\)到\(r_i\)這\(r_i+1\)個選擇。

\(n\)的約數個數即為

\[\LARGE d(n)=(r_1+1)\times (r_2+1) \times ... \times (r_k+1) =\prod_{i=1}^k(r_i+1)

\]

篩的過程中需要儲存\(n\)的最小質因子的出現個數即\(r_1\),原因後面會說明:

  • \(d[i]\)記錄\(i\)的約數個數
  • \(num[i]\)記錄\(i\)的最小質因數個數

分情況讨論:

(一)、\(i\)是質數

\[\large \left\{\begin{matrix}

d[i]=2 & \\

num[i]=1 &

\end{matrix}\right.

\]

  • \(d[i]\):\(i\)是質數,是以約數隻有\(1\)和自身,約數個數為\(2\)
  • \(num[i]\):\(i\)是質數,最小質因子就是自己,個數是\(1\)

(二)、\(i\)取模枚舉的第\(j\)個質數為\(0\), \(primes[j]\)整除\(i\)

\(Q1:\)我們在研究哪些數字之間的遞推關系?

\(A:\)既然是遞推,一般都是已知小的推出大的,這裡應該是已知\(d[i]\)推出\(d[i*primes[j]]\)的值。

\(Q2\):\(d[i*primes[j]]\)是啥?

\(A\): \(primes[j]\)是\(i\)的因子,\(i*primes[j]\)唯一分解不會産生新的質因子,依然是\(p_1,p_2,...p_k\)

\(primes[j]\)從小到大枚舉,是以一定是\(i\)的最小質因子!是以\(p_1^{r_1+1}\),按唯一分解定理展開:

\[\large d[i*primes[j]]=(1+r_1+1)*......*(1+r_k)

\]

\(Q3\):\(d[i∗primes[j]]\)怎麼從\(d[i]\)轉移?

這個時候就可以用到我們之前維護的\(num[i]\)了。

轉移也就非常簡單了:

\[\large d[i∗primes[j]]=d[i]/(num[i]+1)∗(num[i]+2)

\]

解釋:

\[\large \left\{\begin{matrix}

d(i)=(1+r_1)*......*(1+r_k) & ①\\

d(i*primes[j])=(1+r_1+1)*......*(1+r_k) & ②\\

\end{matrix}\right.

\]

使用瞪眼大法(觀察法),找出兩者之間的遞推關系,很明顯,就是 ①式除以\(1+r_1\),再乘上\(1+r_1+1\)即可轉化為 ②式,這下明白為什麼要記錄\(num[i]\)最小質因子的個數了吧~

\(Q4\):那麼\(num[i∗primes[j]]\)是不是也需要從\(num[i]\)進行轉移呢?

\(A\):\(num[i*primes[j]]\)也要轉移,加上最小質因子\(primes[j]\)的貢獻也就是:

\[\large num[i∗primes[j]]=num[i]+1

\]

(三)、目前數取模枚舉的第\(j\)個質數不為\(0\),即 \(primes[j]\) 不能整除 \(i\)

首先我們知道\(i∗primes[j]\)這個數中之前一定不包含\(primes[j]\)這個質因數,

那麼約數個數要加上\(prime[j]\)的,也就是:

\[\large d[i*primes[j]]=(1+r_1)*...*(1+r_k)*(1+1) \\

=d[i]*2=d[i]*d[primes[j]] \]

然後對于最小質因子,因為\(j\)是從小到大枚舉的,是以\(i∗primes[j]\)這個數的最小質因子也就是\(primes[j]\)

是以就可以得到:

\[\large num[i*primes[j]]=1

\]

綜上,就可以寫出篩質因數個數的代碼了:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1010;

int primes[N], cnt; // primes[]存儲所有素數
bool st[N];         // st[x]存儲x是否被篩掉
int d[N];           // d[x]表示x的約數個數
int num[N];         // num[x]表示x的最小質因數的個數
int n;

//歐拉篩法+求約數個數
void get_primes(int n) {
    d[1] = 1; // 1的約數隻有1個,這個比較特殊

    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            // i是質數
            d[i] = 2;   //約數個數是2個,一個是1,另一個是i
            num[i] = 1; //最小質因子個數是1,最小質因子就是自己i
        }

        for (int j = 0; i * primes[j] <= n; j++) {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) {
                d[i * primes[j]] = d[i] / (num[i] + 1) * (num[i] + 2);
                num[i * primes[j]] = num[i] + 1;
                break;
            } else {
                // d[i * primes[j]] = d[i] * d[primes[j]]; 等價于下面的代碼 
                d[i * primes[j]] = d[i] * 2;
                num[i * primes[j]] = 1;
            }
        }
    }
}

int main() {
    scanf("%d", &n);
    get_primes(n);
    //輸出1~n之間所有數字的約數個數
    for (int i = 1; i <= n; i++) printf("%d %d\n", i, d[i]);
    return 0;
}      

二、約數和

設\(sd[i]\)表示\(i\)的約數和

根據算數基本定理得:​​約數和參考連結​​

\[\large sd[n]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})

\]

那麼根據這個式子就可以開始幹了。。。

設\(num[i]\)表示最小質因子的那一項:

\[\large num[i]= p_1^0+p_1^1+p_1^2+……+p_1^{r_1}

\]

分情況讨論:

(一)、\(i\)是質數

\[\large sd[i]=num[i]=i+1

\]

解釋:

  • \(i\)是質數,它的約數隻有自己和\(1\),約數和是\(i+1\)
  • \(i\)是質數,質因子分解也隻能分解出自己,\(sd[i]=i^0+i^1=1+i\)

(二)、\(i\)取模枚舉的質數\(primes[j]\)不為0,即\(primes[j]\)不能整除\(i\)

注:下面的讨論中使用\(p_j\)代替\(primes[j]\)

\(i∗p_j\)裡原先沒有\(p_j\)這一項,加上這一項之後:

\[\large \because sd[i]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})

\]

\[\large \because sd[i*p_j]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})*(p_j^0+p_j^1)

\]

\(\large \because p_j\) 是質數 \(\large \therefore sd[p_j]=p_j^0+p_j^1\)

\(\large \therefore sd[i*p_j]=sd[i]*sd[p_j]\) 注:積性函數

同時更新一下\(num[i∗p_j]\)

\[\large num[i∗p_j]=num[p_j]=p_j^0+p_j^1=1+p_j

\]

解釋:

因為質因子從小到大枚舉,那麼\(i∗p_j\)的最小質因子就應該是\(p_j\),那麼\(num[i∗p_j]\)也就應該等于\(num[p_j]\)

(三)、\(i\)取模枚舉的質數\(j\)為0,即 \(primes[j]\)整除\(i\)

那麼\(sd[i∗p_j]\)中的第一項也就是\(num[i∗p_j]\)也一定是\(p_j\)的第一項

解釋:(等比數列變形小技巧)

\(1+p_i+p_i^2+……+p_i^{r_i}\)這個時候要變成\(1+p_i+p_i^2+……+p_i^{r_i}+p_i^{r_i+1}\),那麼隻需要所有的都乘以一個\(p_i\),然後再加一個\(1\)就好了。

\[\large (1+p_i+p_i^2+……+p_i^{r_i})*p_i+1= \\

1+p_i+p_i^2+……+p_i^{r_i}+p_i^{r_i+1}\]

結論:

\[\large num[i∗p_j]=num[i]∗p_j+1

\]

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 1e3 + 10;

int primes[N], cnt;
bool st[N];

int num[N]; //最小質因子p1組成的等比序列 p1^0+p1^1+...+p1^r1
int sd[N]; //約數和

int n;

void get_primes(int n) {
    sd[1] = 1; // 1的約數隻有自己,約數和是1

    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            //質數
            sd[i] = num[i] = i + 1; // 1和自身是約數,約數和為i+1; 同時因為i是質數,是以隻有一個分拆項,并且分拆項内容=pj^0+pj^1=1+pj=1+i
        }
        for (int j = 0; primes[j] * i <= n; j++) {
            st[i * primes[j]] = true;

            if (i % primes[j]) {
                sd[i * primes[j]] = sd[i] * sd[primes[j]]; //積性函數
                num[i * primes[j]] = primes[j] + 1;
            } else {
                sd[i * primes[j]] = sd[i] / num[i] * (num[i] * primes[j] + 1);
                num[i * primes[j]] = num[i] * primes[j] + 1;
                break;
            }
        }
    }
}

int main() {
    scanf("%d", &n);
    get_primes(n);
    //約數和要不要自己本身,這裡是不想要自己本身
    for (int i = 1; i <= n; i++) printf("%d ", sd[i] - i);
    puts("");
    return 0;
}      
上一篇: ObjC 源碼