update 1-17 新增:線性篩約數個數、線性篩約數和html
若一個定義在正整數域上的函數\(f(x)\)對於任意知足\(\gcd(x,y)==1\)的\(x,y\)都有\(f(xy)=f(x)*f(y)\),則\(f(x)\)是積性函數。函數
\(\mu(n)\):莫比烏斯函數
\(\varphi(n)\):歐拉函數
\(d(n)\):一個數\(n\)的約數個數
\(\sigma(n)\):一個數\(n\)的約數和
\(f(x)=x^k(k\in{N})\):這個玩意兒也是積性函數
更多詳見百度。spa
若\(f(x),g(x)\)都是積性函數,則它們的狄利克雷卷積\(h(x)=\sum_{d|x}f(d)g(\frac xd)\)也是積性函數。code
能夠線性篩。注意是任意積性函數均可以線性篩。htm
一個在嚴格\(O(n)\)時間複雜度內篩出某個東西的東西。blog
保證每一個數只會被它的最小質因子給篩掉(不一樣於埃氏篩中每一個數會被它全部質因子篩一遍從而使複雜度太高)get
int pri[N],tot,zhi[N];//zhi[i]爲1的表示不是質數 void sieve() { zhi[1]=1; for (int i=2;i<=n;i++) { if (!zhi[i]) pri[++tot]=i; for (int j=1;j<=tot&&i*pri[j]<=n;j++) { zhi[i*pri[j]]=1; if (i%pri[j]==0) break; } } }
全部線性篩積性函數都必須基於線性篩素數。class
int mu[N],pri[N],tot,zhi[N]; void sieve() { zhi[1]=mu[1]=1; for (int i=2;i<=n;i++) { if (!zhi[i]) pri[++tot]=i,mu[i]=-1; for (int j=1;j<=tot&&i*pri[j]<=n;j++) { zhi[i*pri[j]]=1; if (i%pri[j]) mu[i*pri[j]]=-mu[i]; else {mu[i*pri[j]]=0;break;} } } }
int phi[N],pri[N],tot,zhi[N]; void sieve() { zhi[1]=phi[1]=1; for (int i=2;i<=n;i++) { if (!zhi[i]) pri[++tot]=i,phi[i]=i-1; for (int j=1;j<=tot&&i*pri[j]<=n;j++) { zhi[i*pri[j]]=1; if (i%pri[j]) phi[i*pri[j]]=phi[i]*phi[pri[j]]; else {phi[i*pri[j]]=phi[i]*pri[j];break;} } } }
記\(d(i)\)表示\(i\)的約數個數
\(d(i)=\prod_{i=1}^{k}(a_i+1)\)
維護每個數的最小值因子出現的次數(即\(a_1\))便可百度
int d[N],a[N],pri[N],tot,zhi[N]; void sieve() { zhi[1]=d[1]=1; for (int i=2;i<=n;i++) { if (!zhi[i]) pri[++tot]=i,d[i]=2,a[i]=1; for (int j=1;j<=tot&&i*pri[j]<=n;j++) { zhi[i*pri[j]]=1; if (i%pri[j]) d[i*pri[j]]=d[i]*d[pri[j]],a[i*pri[j]]=1; else {d[i*pri[j]]=d[i]/(a[i]+1)*(a[i]+2);a[i*pri[j]]=a[i]+1;break;} } } }
記\(\sigma(i)\)表示\(i\)的約數和
\(\sigma(i)=\prod_{i=1}^{k}(\sum_{j=0}^{a_i}p_i^j)\)
維護\(low(i)\)表示\(i\)的最小質因子的指數次冪,即\(p_1^{a_1}\),\(sum(i)\)表示\(i\)的最小質因子對答案的貢獻,即\(\sum_{j=0}^{a_1}p_1^j\)
(這玩意兒可能會爆int吧,我這裏就無論那麼多了)date
int low[N],sum[N],sigma[N],pri[N],tot,zhi[N]; void sieve() { zhi[1]=low[1]=sum[1]=sigma[1]=1; for (int i=2;i<=n;i++) { if (!zhi[i]) low[i]=pri[++tot]=i,sum[i]=sigma[i]=i+1; for (int j=1;j<=tot&&i*pri[j]<=n;j++) { zhi[i*pri[j]]=1; if (i%pri[j]==0) { low[i*pri[j]]=low[i]*pri[j]; sum[i*pri[j]]=sum[i]+low[i*pri[j]]; sigma[i*pri[j]]=sigma[i]/sum[i]*sum[i*pri[j]]; break; } low[i*pri[j]]=pri[j]; sum[i*pri[j]]=pri[j]+1; sigma[i*pri[j]]=sigma[i]*sigma[pri[j]]; } } }
若想線性篩出積性函數\(f(x)\),就必須可以快速計算出一下函數值:
一、\(f(1)\)
二、\(f(p)\)(\(p\)是質數)
三、\(f(p^k)\)(\(p\)是質數)
其實就是含有的質因子數小於等於1的全部數對應的函數值。
常見的積性函數都會給出上述函數值的有關定義。對於自定義的一個積性函數(如狄利克雷卷積),就須要自行計算出上述函數值。
咱們假設已經完成了上述函數值的計算,如今要求篩出全部至少含有兩個質因子的數對應的函數值。
顯然,一個含有至少兩個質因子的數必定能夠被分解成兩個互質的且均不爲1的數的乘積。此時咱們就能夠用\(f(xy)=f(x)f(y)\)計算得出相應的函數值。
如下內容須要徹底理解上面的線性篩素數。
咱們考慮篩的過程當中,\(i*pri_j\)會被\(i\)乘上\(pri_j\)給篩掉。
若將\(i\)惟一分解獲得\(p_1^{a_1}p_2^{a_2}...p_k^{a_k}\),則必定有\(pri_j<=p_1\)。
這個不須要證實,由於當\(pri_j=p1\)的時候就break
掉了。
若\(pri_j<p_1\),則\(pri_j\)與\(i\)互質,能夠直接\(f(i*pri_j)=f(i)*f(pri_j)\)
若\(pri_j=p_1\),這時就須要對\(i\)記錄一個\(low_i\),表示\(i\)中最小值因子的指數次冪,即\(low_i=p_1^{a_1}\)(就是在惟一分解中的那個\(p_1^{a_1}\))。
若是使用\(i\)除掉\(low_i\)那麼結果中的最小質因子必定大於\(p_1\),而又由於\(pri_j=p_1\),從而可知\(\gcd(i/low_i,low_i*pri_j)=1\)。那麼就能夠\(f(i*pri_j)=f(i/low_i)*f(low_i*pri_j)\)
注意當\(low_i=i\)時表示這個數是一個質數的若干次冪,這個時候就須要用到上方的特殊定義。
void sieve() { zhi[1]=low[1]=1; f[1]=對1直接定義; for (ll i=2;i<=n;i++) { if (!zhi[i]) low[i]=pri[++tot]=i,f[i]=對質數直接定義; for (ll j=1;j<=tot&&i*pri[j]<=n;j++) { zhi[i*pri[j]]=1; if (i%pri[j]==0) { low[i*pri[j]]=low[i]*pri[j]; if (low[i]==i) f[i*pri[j]]=對質數的若干次冪進行定義(通常由f[i]遞推); else f[i*pri[j]]=f[i/low[i]]*f[low[i]*pri[j]]; break; } low[i*pri[j]]=pri[j]; f[i*pri[j]]=f[i]*f[pri[j]]; } } }
對於某種形如狄利克雷卷積形式的函數\(\sum_{d|x}f(d)g(\frac xd)\),若其中\(f(x)\)或\(g(x)\)不是積性函數,對於數據範圍較小(如\(10^6\))的時候能夠考慮暴力篩,即枚舉一個d去計算能夠給哪些x作貢獻,複雜度是\(O(\sum_{i=1}^{n}\lfloor\frac ni\rfloor)\)即埃篩的複雜度。若數據範圍較大(\(10^7\)埃篩跑不過)就須要去考慮這個函數的一些相關性質了。
實踐樣例能夠參考:
【BZOJ4407】於神之怒增強版
【BZOJ4804】歐拉心算