積性函數與線性篩
update 1-17 新增:線性篩約數個數、線性篩約數和
積性函數
若一個定義在正整數域上的函數\(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})\):這個玩意兒也是積性函數
更多詳見百度。
狄利克雷卷積
若\(f(x),g(x)\)都是積性函數,則它們的狄利克雷卷積\(h(x)=\sum_{d|x}f(d)g(\frac xd)\)也是積性函數。
積性函數的性質
可以線性篩。注意是任意積性函數都可以線性篩。
線性篩
一個在嚴格\(O(n)\)時間復雜度內篩出某個東西的東西。
線性篩素數
保證每個數只會被它的最小質因子給篩掉(不同於埃氏篩中每個數會被它所有質因子篩一遍從而使復雜度過高)
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;
}
}
}
所有線性篩積性函數都必須基於線性篩素數。
線性篩莫比烏斯函數
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吧,我這里就不管那么多了)
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)\),就必須能夠快速計算出一下函數值:
1、\(f(1)\)
2、\(f(p)\)(\(p\)是質數)
3、\(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】歐拉心算