积性函数与线性筛


积性函数与线性筛

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,则\(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】欧拉心算