1. 程式人生 > >數學 線性篩約數個數和,約數和

數學 線性篩約數個數和,約數和

線性篩約數個數和,約數和

一,線性篩約數個數和

​ 根據唯一分解定理,可得:
\[ n=p_1^{r_1}*p_2^{r_2}*……*p_k^{r_k} \]
​ 對於每個n的約束,肯定是由以上質因數\(p_k\)相乘得來的,那麼根據乘法原理,每個質因數都可以選擇\(0\)\(r_k\)\(r_k+1\)個選擇。

​ 那麼n的約數個數即為
\[ d(n)=\prod_{i=1}^k(r_i+1) \]
篩的過程中需要儲存n的最小質因子的出現個數即\(r_1\)

我們設d(i)表示\(i\)的約數個數和,num(i)表示i的最小質因數的個數。

那麼就可以愉快地分情況討論了。

(一),如果當前數是素數,那麼可得:
\[ d(i)=2\\num(i)=1 \]


(二),當前數取模列舉的第j個素數不為0,即\(i%prime[j]!=0\)

​ 我們要去更新\(i*prime[j]\)的有關資訊。

​ 首先我們知道\(i*prime[j]\)這個數中之前一定不包含\(prime[j]\)這個質因數。

​ 那麼約數個數和就要加上\(prime[j]\)的,也就是:
\[ d(i*prime[j])=(1+r_1)*……*(1+r_k)*(1+1)\\=d(i)*d(prime[j]) \]
​ 然後對於最小質因子,因為j是從小到大列舉的,所以\(i*prime[j]\)這個數的最小質因子也就是\(prime[j]\)

​ 所以就可以得到:
\[ num(i*prime[j])=1 \]


(三),當前數取模列舉的第j個素數為0,即\(j%prime[j]==0\)

​ 依舊要去更新\(i*prime[j]\)的資訊。

​ 這個時候\(i*prime(j)\)中已經存在\(prime[j]\)這個質因子了,並且\(prime[j]\)也一定是\(i*prime[j]\)的最小質因子,所以就可以得到:
\[ d(i*prime[j])=(1+r_1+1)*……*(1+r_k) \]
那麼怎麼從\(d(i)\)轉移呢?

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

轉移也就非常簡單了:
\[ d(i*prime[j])=d(i)/(num(i)+1)*(num(i)+2) \]


num也要轉移,加上最小質因子\(prime[j]\)的貢獻也就是:
\[ num(i*prime[j])=num(i)+1 \]
綜上,就可以寫出篩質因數個數的程式碼了。

code:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int wx=1017;

int isprime[wx],prime[wx],d[wx],num[wx];
int tot,n,m;

inline int read(){
    int sum=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0'; ch=getchar();}
    return sum*f;
}

void Euler(){
    memset(isprime,1,sizeof isprime); d[1]=1;
    for(int i=2;i<=n;i++){
        if(isprime[i]){
            prime[++tot]=i;
            d[i]=2;
            num[i]=1;
        }
        for(int j=1;j<=tot&&i*prime[j]<=n;j++){
            isprime[i*prime[j]]=0;
            if(i%prime[j]==0){
                d[i*prime[j]]=d[i]/(num[i]+1)*(num[i]+2);
                num[i*prime[j]]=num[i]+1; break;
            }
            else{
                d[i*prime[j]]=d[i]*d[prime[j]];
                num[i*prime[j]]=1;
            }
        }
    }
}



int main(){
    n=read(); Euler();
    for(int i=1;i<=n;i++)printf("%d %d\n",i,d[i]);
    return 0;
}

二,線性篩約數和

我們設\(sd(i)\)表示i的約數和。

在算數基本定理中,可以得:
\[ sd(n)=(1+p_1+p_1^2+……+p_1^{r_1})*(1+p_2+p_2^2+……+p_2^{r_2})*……*(1+p_k+p_k^2+……+p_k^{r_k}) \]
​ 那麼根據這個式子就可以開始幹了。。。

​ 這個時候我們需要記錄最小質因子的那一項也就是\((1+p_1+p_1^2+……+p_1^{r_1})\)

可以設\(sd(i)\)表示i的約數和。設\(num(i)\)表示我們需要記錄的最小質因子的那一項(等比數列?)。

好了,開始分情況討論吧。

(一),當前數是一個素數:

​ 易知:
\[ sd(i)=i+1\\num(i)=i+1 \]
(二),當前數取模列舉的質數不等於0

​ 易知\(i*prime[j]\)裡原先沒有\(prime[j]\)這一項,加上這一項之後可得:
\[ sd(i*prime[j])=sd(i)*sd(prime[j]) \]
​ (好吧我又犯懶了。。。但是思路是和上面一樣的)

​ 同時更新一下\(num(i*prime[j])\)
\[ num(i*prime[j])=1+prime[j] \]
這是因為質因子從小到大列舉,那麼\(i*prime[j]\)的最小質因子就應該是\(prime[j]\),那麼\(num(i*prime[j])\)也就應該等於\(num(prime[j])\)

(三),當前數取模列舉的質數等於0

​ 那麼\(sd(i*prime[j])\)中的第一項也就是\(num(i*prime[j])\)一定是\(prime[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\)也就是\(prime[j]\),然後再加一個一就好了。

​ 即:
\[ d(i*prime[j])=(d(i)/num(i)*(num(i)*prime[j])+1 \]
然後\(num(i*prime[j])\)依舊是\(prime[j]\)這一項,那麼就是:
\[ num(i*prime[j])=num(i)*prime[j]+1 \]
這樣,我們又可以開始愉快的寫程式碼啦。。。

code:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int wx=1017; 

inline int read(){
    int sum=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0'; ch=getchar();}
    return sum*f;
}

int isprime[wx],sd[wx],num[wx],prime[wx];
int n,tot;

void Euler(){
    memset(isprime,1,sizeof isprime); sd[1]=1;
    for(int i=2;i<=n;i++){
        if(isprime[i]){
            prime[++tot]=i;
            sd[i]=1+i; num[i]=1+i;
        }
        for(int j=1;j<=tot&&prime[j]*i<=n;j++){
            isprime[i*prime[j]]=0;
            if(i%prime[j]!=0){
                sd[i*prime[j]]=sd[i]*sd[prime[j]];
                num[i*prime[j]]=prime[j]+1;
            }
            else{
                sd[i*prime[j]]=sd[i]/num[i]*(num[i]*prime[j]+1);
                num[i*prime[j]]=num[i]*prime[j]+1; break;
            }
        }
    }
}

int main(){
    n=read(); Euler();
    for(int i=1;i<=n;i++)printf("%d %d\n",i,sd[i]);
    return 0;
}