數論函式(二)
轉載請註明
目錄
1.前言
這一篇部落格是以“數論函式(一)”為基礎的。本文主要研究:
1.討論求解因子問題時使用的篩法,包括了普通篩法,Eratosthenes篩法,尤拉篩法等,主要討論的是線性篩法
2.利用杜教篩在低於線性時間複雜度快速求解積性函式字首和一類的問題
2.篩法(以篩素數為例子)
2.1普通篩法一O()
#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()
#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()
#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( )
#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()
其中,有關積性函式的一些基本概念已經在“數論函式(一)”中涉及到了,這裡不再贅述
4.3杜教篩的基本套路
假設 是積性函式,設 , 設 (S(n)是我們要求的那一部分)
那麼就有
=>
=>
=>
=> (最後就是求S(n),也就是到了求這一部分)
為了保證好求S(n):
1.保證 好求 (f和g的卷積和好求)(注:單位元e的字首和為1)
2.分塊+記憶化遞迴,以O()求出 S(n)
4.4兩個注意點
4.4.1第一個注意點
若在使用杜教篩的過程中,等好的右邊得到了 。那麼,i 從1~n中, 有不超過 個值,前個值 分別一 一對應後 個值。
比如:設n=20,那麼有:
前: 20(20/1), 10(20/2), 6(20/3), 5(20/4),
後 : 1(20/20) , 2(20/10), 3(20/6), 4(20/5),
而且,對於每一個固定的 ,i 的浮動範圍是
4.4.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;
}
}
在這裡,我們根據公式
得到,
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;
}
}
在這裡,我們根據公式
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
在此,像以上原創作者表示感謝
相關文章
- 數論函式群在數論多項式生成函式集上的作用函式
- 【進階】數論函式求和(理論)函式
- Contest5408 - 數論函式函式
- 概率論08 隨機變數的函式隨機變數函式
- 數論函式從入門到進門函式
- HDU 4279 2012網路賽Number(數論 尤拉函式結論約數個數)函式
- Java中的函數語言程式設計(二)函式式介面Functional InterfaceJava函數程式設計函式Function
- 字元函式、數字函式和日期函式字元函式
- 【GO學習二】包,函式,常量和變數Go函式變數
- 分散式理論(二) - BASE理論分散式
- 二級指標,二維陣列函式引數傳遞指標陣列函式
- 函式定義、函式的引數、函式的預設引數函式
- 【函式】Oracle函式系列(2)--數學函式及日期函式函式Oracle
- 聚合函式與數字函式函式
- 正規表示式replace()函式第二個引數$&的作用函式
- JavaScript replace()第二個引數為函式時的引數JavaScript函式
- JavaScript函數語言程式設計,真香之組合函式(二)JavaScript函數程式設計函式
- 數論線性篩總結 (素數篩,尤拉函式篩,莫比烏斯函式篩,前n個數的約數個數篩)函式
- MySQL函式大全(字串函式,數學函式,日期函式,系統級函式,聚合函式)MySql函式字串
- 函式引數 引數定義函式型別函式型別
- Oracle 函式大全(字串函式,數學函式,日期函式,邏輯運算函式,其他函式)Oracle函式字串
- Java 函數語言程式設計(二)Lambda表示式Java函數程式設計
- Javascript函式引數求值——Thunk函式JavaScript函式
- 函式基礎和函式引數函式
- numtoyminterval函式——數字轉換函式函式
- 函式學習二函式
- 數學函式函式
- ORACLE單行函式與多行函式之二:字元函式示例Oracle函式字元
- P5655 基礎數論函式練習題 題解函式
- 素數計數函式函式
- Oracle OCP(03):字元函式、數字函式和日期函式Oracle字元函式
- 函式外與函式內的變數函式變數
- PHP函式,引數,可變參函式.PHP函式
- 有標號的二分圖計數 [生成函式 多項式]函式
- C++行內函數、函式過載與函式預設引數C++函數函式
- 理解函數語言程式設計中的函式組合--Monoids(二)函數程式設計函式Mono
- ORACLE單行函式與多行函式之三:數值函式Oracle函式
- 字串函式學習二字串函式