數論函式(二)

Ivanzn發表於2019-03-24

轉載請註明

 

目錄

轉載請註明

 

1.前言

2.篩法(以篩素數為例子)

2.1普通篩法一O()

2.2.普通篩法而O()

2.3Eratosthenes篩法 O()

2.4尤拉篩(線性篩篩素數)O(    )

3.線性篩

3.1篩素數(也叫做尤拉篩)

3.2篩尤拉函式

3.3篩莫比烏斯函式

3.4篩最小質因子

3.5篩最大質因子

3.6篩不同種類質因子數目

3.7篩所有因子數目

3.8篩所有因子和

3.9發現

4.杜教篩

4.1線性篩和杜教篩

4.2杜教篩的主要功能

4.3杜教篩的基本套路

4.4兩個注意點

4.4.1第一個注意點

4.4.2第二個注意點

4.5杜教篩的應用

4.5.1求前n數的約數和(第一個注意點的情況)

4.5.2求前n個數的約數個數(第一個注意點的情況)

4.5.3求莫比烏斯函式的字首和

4.5.4求尤拉函式的字首和

4.5.5杜教篩的套路總結

5.參考文獻


1.前言

這一篇部落格是以“數論函式(一)”為基礎的。本文主要研究:

1.討論求解因子問題時使用的篩法,包括了普通篩法,Eratosthenes篩法,尤拉篩法等,主要討論的是線性篩法

2.利用杜教篩在低於線性時間複雜度快速求解積性函式字首和一類的問題

 

2.篩法(以篩素數為例子)

2.1普通篩法一O(n^2)

#include<bits/stdc++.h>
const int N = 1e5+200;
using namespace std;
int prime[N];
void init()
{
	int cnt=0;
	for(int i=1;i<=N;++i)
	{
		int flag=1;
		for(int j=2;j<=i;++j)
		{
			if(i%j==0)
			{
				flag=0;
				break;
			}
		}
		if(flag)	prime[++cnt]=i;
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<prime[n]<<endl;
}

 

2.2.普通篩法而O(n \cdot \sqrt n)

#include<bits/stdc++.h>
const int N = 1e5+200;
using namespace std;
int prime[N];
void init()
{
	int cnt=0;
	for(int i=2;i<=N;++i)
	{
		int flag=1;
		for(int j=2;j*j<=i;++j)
		{
			if(i%j==0)	{flag==0;break;}
		}
		if(flag)	prime[++cnt]=i;
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<prime[n]<<endl;
}

 

2.3Eratosthenes篩法 O(n \cdot log(log n))

#include<bits/stdc++.h>
const int N = 1e5+200;
using namespace std;
int prime[N];
bool vis[N];
void init()
{
	int cnt=0;
	for(int i=2;i<=N;++i)
	{
		if(!vis[i])	prime[++cnt]=i;
		for(int j=2;j*i<=N;++j)
		{
			vis[j]=1;
		}
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<prime[n]<<endl;
}

 

2.4尤拉篩(線性篩篩素數)O(  \frac{3}{2} \cdot n  )

#include<bits/stdc++.h>
const int N = 1e5+200;
using namespace std;
int prime[N];
bool vis[N];
void init()
{
	int cnt=0;
	for(int i=2;i<=N;++i)
	{
		if(!vis[i])	prime[++cnt]=i;
		for(int j=1;i*prime[j]<=N&&j<=cnt;++j)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)
				break;
		}
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<prime[n]<<endl;
}

解釋:

在尤拉篩中,我們必須要知道那個關鍵步驟:“if(i%prime[j]==0) break;”

在上面的表格中,我們發現,如果按照最小因子*剩餘因子 = 合數的形式的話,就能保證無冗餘。

為什麼呢?

因為,若i%prime[j]==0,等價於i = k* prime[j],所以 i * prime[j+1] = k' * prime[j] ,就等於重複篩了。  

由打表發現,每次用尤拉篩的時候,到n/2的地方,就已經全部篩完了,剩下的(n/2+1,n)之間都沒有,所以尤拉篩的時間複雜度的下限是 3/2 n

 

說明:線性篩法更加快速,沒有重複篩(沒有冗餘),那麼就有線性篩法的時間複雜度趨近於線性時間複雜度。

 

3.線性篩

3.1篩素數(也叫做尤拉篩)

上面提到過,這裡不再贅述

3.2篩尤拉函式

#include<bits/stdc++.h>
const int N = 2e7;
using namespace std;
int prime[N+20],phi[N+20],cnt;
bool vis[N+20];
void init()
{
	phi[1]=1;
	cnt=0;
	memset(phi,0,sizeof(phi));
	for(int i=2;i<=N;++i)
	{
		if(!vis[i])//if(!phi[i])
		{
			prime[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)
			{ 
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else	phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<phi[n]<<endl;
}

 

這裡,使用了兩個性質:(p是質數)

性質一:i % p ==0: phi(i * p) = phi(i) * p

性質二:i % p !=0   phi(i * p) = phi(i) * (p-1)

 

3.3篩莫比烏斯函式

#include<bits/stdc++.h>
const int N = 2e7;
using namespace std;
int prime[N+20],mu[N+20],cnt;
bool vis[N+20];
void init()
{
	cnt=0;
	mu[1]=1;
	memset(vis,0,sizeof(vis));
	for(int i=2;i<=N;++i)
	{
		if(!vis[i])	
		{
			prime[++cnt]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				mu[i*prime[j]]=0;
				break;
			}
			else	mu[i*prime[j]]=-mu[i];
		}
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<mu[n]<<endl;
}

 

在這裡,用到了三個東西來篩莫比烏斯函式:(p是質數)
 

一、平方數,比如2^2,3^2,...n^2,一定滿足i%p=0,因此,mu(平方數)=0;

二、若 i%p==0,那麼有 i=k*p,那麼 i*p= p^2 * q,那麼 i * p就有平方因子,所以,mu(i*p) = 0,

三、若i % p != 0,   我們分三種情況,第一:i是質數,又因為p是質數,所以mu(i * p) = (-1)^2 = 1 = - mu(p)

第二:i 含有平方根因子,又因為平方數已經被篩過了,也滿足mu(i * p) = 0 = -mu(i),

第三,i 不含有平方根因子,那麼再加上p這個因子,也滿足mu(i * p) = -mu(p) = -mu(i) 

因此,有mu(i * p) = -mu(i)

 

3.4篩最小質因子

#include<bits/stdc++.h>
const int N = 2e7;
using namespace std;
int prime[N+20],mnfact[N+20],cnt;
bool vis[N+20];
void init()
{
	cnt=0;
	mnfact[1]=1;
	memset(vis,0,sizeof(vis));
	for(int i=2;i<=N;++i)
	{
		mnfact[i]=min(mnfact[i],i);
		if(!vis[i])
		{
			prime[++cnt]=i;
			mnfact[i]=i;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
		{
			vis[i*prime[j]]=1;
			mnfact[i*prime[j]]=prime[j];
			if(i%prime[j]==0)
				break;
		}
	}
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<mnfact[n]<<endl;
}

 

由於線性篩使用的本來就是使用最小因子這個概念,所以,很容易就寫成了。

 

3.5篩最大質因子

#include<bits/stdc++.h>
const int N = 2e7;
using namespace std;
int prime[N+20],mxfact[N+20],cnt;
bool vis[N+20];
void init()
{
    cnt=0;
    memset(mxfact,0,sizeof(mxfact));
    mxfact[1]=1;
    for(int i=2;i<=N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            mxfact[i]=i;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
        {
            vis[i*prime[j]]=1;
            mxfact[i*prime[j]]=max(mxfact[i],prime[j]);
            if(i%prime[j]==0)
            {
                break;
            }
        }
    }
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<mxfact[n]<<endl;
}

 

這裡,分成兩種情況:(p為質數)

一、i是質數,那麼 mxfact[ i ] = i;

二、i不是質數,就有 i = k* p, mxfact[ i* p ] = max( p,mxfact[ i ] )

 

3.6篩不同種類質因子數目

#include<bits/stdc++.h>
using namespace std;
const int N= 2e7;
int prime[N+20],difffact[N+20],cnt;
bool vis[N+20];
void init()
{
    cnt=0;
    memset(difffact,0,sizeof(difffact));
    difffact[1]=1;
    for(int i=2;i<=N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            difffact[i]=2;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                difffact[i*prime[j]]=difffact[i];
                break;
            }
            else
                difffact[i*prime[j]]=difffact[i]+1;
        }
    }
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<difffact[n]<<endl;
}

 

這裡,分成了三種情況:(p是質數)

一、i是質數,那麼difffact[ i ] = 2

二、i不是質數,i % p = 0,那麼就有i = k*p, difffact[i * p] = difffact[i](已經包含了p這個因子)

三、i不是質數,i % p != 0 ,那麼就有 difffact[i * p] =difffact[ i ]+1( 加上了p這個因子)

 

3.7篩所有因子數目

#include<bits/stdc++.h>
using namespace std;
const int N= 2e7;
int prime[N+20],allfact[N+20],cnt,mnfact_num[N+20];
bool vis[N+20];
void init()
{
    cnt=0;
    memset(allfact,0,sizeof(allfact));
    memset(mnfact_num,0,sizeof(mnfact_num));
    allfact[1]=1;
    mnfact_num[1]=1;
    for(int i=2;i<=N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            allfact[i]=2;
            mnfact_num[i]=1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                allfact[i*prime[j]]=allfact[i]/(1+mnfact_num[i])*(2+mnfact_num[i]);
                mnfact_num[i*prime[j]]=mnfact_num[i]+1;
                break;
            }
            else
            {
                allfact[i*prime[j]]=allfact[i]*2;
                mnfact_num[i*prime[j]]=1;
            }
        }
    }
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<allfact[n]<<endl;
}

其中,allfact 【x】表示x的約數個數,mnfact_num【x】表示x的最小質因數的個數

 

這裡,分成了三種情況:(p是質數)
一、i是質數,顯然,allfact【i】=2,mnfact_num【i】=1

二、i不是質數,i % p = 0, 那麼 有 i = k* p,假設 i = p * q, 那麼這個時候就有 i * p =p^2 *q,根據 約數個數計算公式,就有

allfact【i * p】 = allfact【i】/ (1 + mnfact_num【i】) * (2 + mnfact_num【i】);(質因子為p的個數增加了一個)

而且,mnfact_num【i * p】 = mnfact_num【i * p】+1;(理由同上)

三、i不是質數,i % p != 0, 那麼有 allfact【i * p】 = allfact【i 】* 2(因為增加了一個新的因子 p,就要乘上(1+p的個數)=》(1+1)=》* 2)

而且,mnfact_num【i * p】=1;

 

3.8篩所有因子和

#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define register int rint
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define mem(a,b) memset(a,b,sizeof(a))
#define PI 3.141592653589793
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>PII;
const int N= 2e7;
const int lim=2e9;
const double esp=1e-6;
tr1::unordered_map<ll,ll>w;
template<typename T>inline void read(T &x)
{
    x=0;
    static int p;p=1;
    static char c;c=getchar();
    while(!isdigit(c)){if(c=='-')p=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<1)+(x<<3)+(c-48);c=getchar();}
    x*=p;
}

int prime[N+20],factsum[N+20],nil_sum[N+20],cnt;
bool vis[N+20];
void init()
{
	cnt=0;
	factsum[1]=1;
	memset(vis,0,sizeof(vis));
    memset(nil_sum,0,sizeof(nil_sum));
    for(int i=2;i<=N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            factsum[i]=1+i;
            nil_sum[i]=1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                factsum[i*prime[j]]=factsum[i]*prime[j]+nil_sum[i];
                nil_sum[i*prime[j]]=nil_sum[i];
                break;
            }
            else
            {
                factsum[i*prime[j]]=factsum[i]*(1+prime[j]);
                nil_sum[i*prime[j]]=factsum[i];
            }
        }
     }
}
int main()
{
	init();
	int n;
	while(cin>>n)	cout<<factsum[n]<<endl;
}

其中,factsum【x】表示x的約數和,nil_sum【x】表示x的約數中不能被x的最小素因子整除的約數和

 

這裡,分成了三種情況:(p是質數)
一、i是質數,顯然,factsum【i】=1+x,nil_sum【i * p】=1;

二、i不是質數,i % p =0, factsum【i】 可以寫成 x + x*p +...+x*p^k ,  factsum【i * p】 可以表示成 x+ x *p+...+x*p^k+x*p^(k+1),

那麼  factsum【i * p】 =  factsum【i】* p +  (factsum【i】中不包含p因子的那一部分的和),在這裡,i的最小因子就是p,所以,那一部分就是 nil_sum【i】

所以 factsum【i * p】 =  factsum【i】* p + nil_sum【i】

而且,nil_sum【i * p】 = nil_sum【i】(因為 i 中 已經包含了因子p,所以沒有改變)

三、i不是質數,i%p != 0 ,因此,p與i互質,所以,相當於增加了一個多項式(1+p),

所以,factsum【i * p】= factsum【i】*(1+p)

而且,有 nil_sum【i * p】= factsum【i】(因為 p 是 i * p 的最小素因子,意味著要除去i * p 中含有p因子的那一個部分的約數和,就等價於factsum【i】)

 

3.9發現

由上這些例子,我們發現,只要是與因子有關的函式,都可以利用線性篩。

 

4.杜教篩

4.1線性篩和杜教篩

在上面,我們見識到了線性篩的魅力和威力,似乎有了一個很好的武器,可為什麼又出來一個杜教篩呢?

首先,觀察上面的篩法,發現n不會超過2e7(其實可以大一點點。那麼考慮一種情況,當n<=1e10或者更大呢?

這個時候,線性時間複雜度也涼了,所以要使用低於線性時間複雜度的方法。

於是,杜教篩誕生了。

 

4.2杜教篩的主要功能

在低於線性時間複雜度下,求解一類積性函式字首和。

時間複雜度的最好情況是 O(n^{\frac{2}{3}}

其中,有關積性函式的一些基本概念已經在“數論函式(一)”中涉及到了,這裡不再贅述

 

4.3杜教篩的基本套路

假設 f 是積性函式,設 h = f * g, 設 S(n) = \sum_{i=1}^{n}f(i) (S(n)是我們要求的那一部分)

那麼就有 

=>\sum_{i=1}^{n}h(i)=\sum_{i=1}^{n}\sum_{d|i}f(d) \cdot g(\frac{i}{d})

=>\sum_{i=1}^{n}h(i)=\sum_{d=1}^{n}g(d)S(\left \lfloor \frac{n}{d} \right \rfloor)

=>\sum_{i=1}^{n}h(i)=g(1) \cdot S(n)+\sum_{d=2}^{n}g(d)S(\left \lfloor \frac{n}{d} \right \rfloor)

=>g(1) \cdot S(n) = \sum_{i=1}^{n} h(i) -\sum_{i=2}^{n}g(i) \cdot S(\left \lfloor \frac{n}{i} \right \rfloor)    (最後就是求S(n),也就是到了求這一部分)

 

為了保證好求S(n):

1.保證  \sum_{i=1}^{n} h(i)  好求 (f和g的卷積和好求)(注:單位元e的字首和為1)

2.分塊+記憶化遞迴,以O(n^{\frac{2}{3}})求出 S(n)

 

4.4兩個注意點

4.4.1第一個注意點

若在使用杜教篩的過程中,等好的右邊得到了  \left \lfloor \frac{n}{i} \right \rfloor 。那麼,i 從1~n中,\left \lfloor \frac{n}{i} \right \rfloor 有不超過 2 \sqrt(n) 個值,前\sqrt(n)個值 分別一 一對應後 \sqrt(n) 個值。

比如:設n=20,那麼有:

\sqrt(n): 20(20/1),   10(20/2),  6(20/3), 5(20/4), 

後 \sqrt(n):   1(20/20) ,   2(20/10), 3(20/6), 4(20/5),  

而且,對於每一個固定的 \left \lfloor \frac{n}{i} \right \rfloor,i 的浮動範圍是[\left \lfloor \frac{n}{\left \lfloor \frac{n}{i}\right \rfloor+1} +1\right \rfloor,\left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor]

 

4.4.2第二個注意點

\sum_{i=1}^{n}i \cdot \left \lfloor \frac{n}{i} \right \rfloor=\sum_{i=1}^{n}\frac{(1+\left \lfloor \frac{n}{i} \right \rfloor) \cdot \left \lfloor \frac{n}{i} \right \rfloor}{2},   這也是一個常用的形式

 

4.5杜教篩的應用

4.5.1求前n數的約數和(第一個注意點的情況)

#include<bits/stdc++.h>
#define register int rint
#define INF 0x3f3f3f3f3f
#define MOD 1000000007
#define mem(a,b) memset(a,b,sizeof(a))
#define PI 3.141592653589793
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>PII;
const int N= 2e5+500;
const int Max=300;
const double esp=1e-6;
inline int rd() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
ll n;
ll ans=0;
int main()
{
    while(cin>>n)
    {
        ans=0;
        ll i,q,a,b;
        for(i=1;i*i<=n;++i)
        {
            ans=(ans+i*(n/i))%MOD;
            a=n/(i+1)+1;
            b=n/i;
            ans=(ans+i*(a+b)*(b-a+1)/2)%MOD;
        }
        i--;
        if(i==n/i)
            ans-=i*(n/i);
        cout<<ans<<endl;
    }
}

 

4.5.2求前n個數的約數個數(第一個注意點的情況)

#include<bits/stdc++.h>
#define register int rint
#define INF 0x3f3f3f3f3f
#define MOD 1000000007
#define mem(a,b) memset(a,b,sizeof(a))
#define PI 3.141592653589793
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>PII;
const int N= 2e5+500;
const int Max=300;
const double esp=1e-6;
inline int rd() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
ll n;
ll ans=0;
int main()
{
    while(cin>>n)
    {
        ans=0;
        ll i,a,b;
        for(i=1;i*i<=n;++i)
        {
            a=n/(1+i)+1;
            b=n/i;
            ans=(ans+(b-a+1)*i)%MOD;
            a=n/(n/i+1)+1;
            b=n/(n/i);
            ans=(ans+(b-a+1)*(n/i))%MOD;
        }
        i--;
        if(i==n/i)
        {
            a=n/(1+i)+1;
            b=n/i;
            ans-=(b-a+1)*i;
        }
        cout<<ans<<endl;
    }
}

 

4.5.3求莫比烏斯函式的字首和

#include<bits/stdc++.h>
#include<tr1/unordered_map>
typedef long long ll;
const int N = 2e7;
using namespace std;
bool vis[N+20];
int prime[N+20],mu[N+20],cnt;
tr1::unordered_map<int,int>w;
void init()
{
    cnt=0;
    mu[1]=1;
    memset(vis,0,sizeof(vis));
    for(int i=2;i<=N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            else    mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i=1;i<=N;++i)   mu[i]+=mu[i-1];
}
int djs(ll n)
{
    if(n<=N)    return mu[n];
    if(w[n])    return w[n];
    int ans;
    ll l,r;
    for(l=2;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans-=(r-l+1)*djs(n/l);
    }
    w[n]=ans;
    return ans;
}
int main()
{
    init();
    ll n;
    while(cin>>n)
    {
        cout<<djs(n)<<endl;
    }
}

在這裡,我們根據公式  e = 1 * \mu

得到,S(n) = 1 -\sum_{i=2}^{n}S(\left \lfloor \frac{n}{i} \right \rfloor)

 

4.5.4求尤拉函式的字首和

#include<bits/stdc++.h>
#include<tr1/unordered_map>
const int MOD=1e9+7;
typedef long long ll;
const int N = 2e7;
using namespace std;
bool vis[N+20];
int prime[N+20],phi[N+20],cnt;
int inv2=5e8 + 4;
tr1::unordered_map<int,int>w;
void init()
{
    cnt=0;
    phi[1]=1;
    memset(vis,0,sizeof(vis));
    for(int i=2;i<=N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;++j)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else    phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
    for(int i=1;i<=N;++i)   phi[i]=(phi[i]+phi[i-1])%MOD;
}
ll djs(ll n)
{
    if(n<=N)    return phi[n];
    if(w[n])    return w[n];
    ll ans=((n%MOD+1)*(n%MOD))%MOD*inv2%MOD;
    ll l,r;
    for(l=2;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans=ans-((r-l+1)%MOD)*djs(n/l)%MOD;
    }
    w[n]=ans;
    return ans;
}
int main()
{
    init();
    ll n;
    while(cin>>n)
    {
        cout<<djs(n)<<endl;
    }
}

在這裡,我們根據公式  S(n) = \frac{n \cdot (n+1)}{2} - \sum_{i=2}^{n}S(\left \lfloor \frac{n}{i} \right \rfloor) 

 

4.5.5杜教篩的套路總結

1.找到一個好的積性函式,從而得到一個好的S(n)公式,以及便於求卷積字首和(f 和 g的卷積字首和)

2.採用分塊 +  unordered_map 進行記憶化遞迴

 

5.參考文獻

1.https://blog.csdn.net/Ruger008/article/details/80245687

2.https://www.cnblogs.com/TheRoadToTheGold/p/8228969.html#_label1

3.https://blog.csdn.net/skywalkert/article/details/50500009

4.https://www.cnblogs.com/peng-ym/p/9446555.html

在此,像以上原創作者表示感謝

相關文章