线性筛及非线性筛法

积性函数

满足\(f(x*y)=f(x)*f(y)\)\(gcd(x,y)=1\)

常见积性函数

\(\phi(x)\)
\(\mu(x)\)
约数个数
约数和

注意

两个积性函数做乘法,卷积形成的函数都是积性函数
所有的积性函数都可以线性筛

线性筛

线性筛保证的每次筛出来的数的最小质因数都是现在枚举的这个质数
所以如果\(i\)\(p[j]\)互质
那么直接利用积性函数的性质乘起来即可
然后如果\(i\)\(p[j]\)不互质
那么就说明\(p[j]\)这个质数已经在i中出现过
那么如果要求出\(i*p[j]\)的积性函数的值的话需要具体的根据需要的积性函数的性质和具体形态来求
通常我们都要记录下\(low[x]=p^{maxa}|x\)
然后只要能快速求出\(f(pmin^k)\)即可求出函数
这样我们就可以快速表示出函数的值了
基本上模板就是这样

void Pre(int n) {
    f[1] = /*1处的函数值*/
    for(int i = 2 ; i <= n ; i ++) {
        if(!notp[i]) {
            p[++pnum] = i ;
            f[i] = /*这时i是一个质数,可以快速确定答案*/
            low[i] = i ; /*i是一个质数,所以ta的最小质因子的最高次幂就是i本身*/
            /*根据需要再记录一些东西*/
        }
        for(int j = 1 ; j <= pnum && 1LL * i * p[j] <= n ; j ++) {
            notp[i * p[j]] = true ;
            if(i % p[j] == 0) { // i 与 p[j] 不互质,这时候情况比较麻烦一些 
                low[i * p[j]] = low[i] * p[j] ; // (i * p[j])的最小质因子p[j]出现了多次 
                if(low[i] == i) { // 表示i*p[j]是p[j]^k,需要快速计算出质数的若干次幂的答案
                    
                }
                else {  // 这时候除掉质数的几次幂后剩下的数不是1,也可以直接计算
                    f[i * p[j]] = f[i / low[i]] * f[low[i * p[j]]] ;
                }
            }
            else { // i 与 p[j] 互质,直接乘起来就行了 
                low[i * p[j]] = p[j] ;
                f[i * p[j]] = f[i] * f[p[j]] ;
            }
        }
    }
}

线性筛欧拉函数

根据欧拉函数的计算公式\(\phi(x)=x\prod_{i=1}^{a_i}(1-\frac{1}{p_i})\)
当是质数的若干次幂的时候,没有新质数的产生,只会影响前面的那个\(x\)

线性筛约数个数

\(x\)的约数个数=\(\prod_{i=1}^{a_i}{num[i]+1}\)
所以计算质数的若干次幂的话只需要记录最小质因子出现了多少次+1即可

线性筛约数和

\(x\)的约数和=\(\prod_{i=1}^{a_i}{\sum_{j=0}^{num[i]}{p[i]^j}}\)
所以计算质数的若干次幂的话只需要记录最小质因子的\(\sum_{i=0}^{num[i]}{pmin^i}\)


杜教筛

狄利克雷卷积

\(\sum_{d|n}{f(d)g(\frac{n}{d})}\)
两个积性函数的狄利克雷卷积还是积性函数

常见完全积性函数

\(e(x)\):元函数,\(e(x)=[x=1]\)
\(I(x)\):恒等函数\(I(x)=1\)
\(id(x)\):单位函数\(id(x)=x\)

常见函数的狄利克雷卷积

\(1.f*e(n)=f(n)\)
根据\(\sum_{d|n}{f(d)e(\frac{n}{d})}\)

\(2.I*\mu(n)=e(n)=[n=1]\)
根据\(\sum_{d|n}{\mu(d)}=[n=1]\)
所以\(\sum_{d|n}{\mu(d)I(\frac{n}{d})}=e=[n=1]\)

\(3.I*\phi(n)=id(n)\)
根据\(\sum_{d|n}{\phi(d)}=n\)
所以\(\sum_{d|n}{\phi(d)I(\frac{n}{d})}=n\)

\(4.\mu*id(n)=\phi(n)\)
因为\(\phi*I(n)=id(n)\)
同时乘\(\mu\)
\(\phi*I*\mu=id*\mu\)
狄利克雷卷积有结合律
\(\phi*e=id*\mu\)
\(\phi=id*\mu\)

杜教筛

假设我们要求一个积性函数的前缀和\(S(n)=\sum_{i=1}^{n}{f(i)}\)
我们可以快速求出ta的前缀和必须能找到函数\(g\)
使得我们可以快速计算出\(g,f*g\)的前缀和
\(\sum_{i=1}^{n}f*g=\sum_{i=1}^{n}\sum_{d|n}{f(d)g(\frac{i}{d})}\)
\(=\sum_{d=1}^{n}g(d)\sum_{i=1}^{\frac{n}{d}}{f(i)}\)
\(=\sum_{d=1}^{n}{g(d)S(\frac{n}{d})}\)

然后考虑\(g(1)S(n)=S(n)=\sum_{d=1}^{n}{g(d)S(\frac{n}{d})}-\sum_{d=2}^{n}{g(d)S(\frac{n}{d})}\)
然后发现前面的那个东西就是\(f*g\)的前缀和
所以\(S(n)=\sum_{d=1}^{n}{f*g}-\sum_{d=2}^{n}{g(d)S(\frac{n}{d})}\)

这样就可以计算了
然后这样的复杂度是\(O(n^{\frac{3}{4}})\)
可以先预处理出\(n^{\frac{2}{3}}\)的答案,这样复杂度就是\(O(n^{\frac{2}{3}})\)

大致代码就长这样

void Solve(int n) {
    if(n<=upp) return sum[n] ; /*小于预处理的值直接返回*/
    if(psum[n]) return psum[n] ;
    int ret = calc() ; // 计算f*g的前缀和
    for(int l = 2/*注意要从2开始*/ , r ; l <= n ; l = r + 1) {
        r = n / (n / l) ;
        ret -= (g(r) - g(l - 1)/*计算l~r之间g的前缀和*/) * Solve(n / l)/*继续递归计算f的前缀和*/ ;
    }
    psum[n] = ret ; return ret ;
}
int main() {
    presolve() ; // 先筛出前一部分的信息
    Solve(n) ;
    return 0 ;
}

有一些常用的能快速计算某些积性函数的前缀和的公式

\(1.sum1[n]=1+2+3+4+..+n=\frac{n(n+1)}{2}\)
\(2.sum2[n]=1^2+2^2+3^2+4^2+..+n^2=\frac{n(n+1)(n*2+1)}{6}\)
\(3.sum3[n]=1^3+2^3+3^3+4^3+..+n^3=sum1[n]^2\)

\(Min\)_\(25\)

可以在\(O(\frac{n^{\frac{3}{4}}}{\log n})\)的时间复杂度内求出一个积性函数的前缀和

对要筛的函数\(f\)的要求:

1.积性函数
2.可以用一个多项式来表示
3.可以快速求出\(f(p^c)\)

对于积性函数\(f\)
我们先只考虑\(n\)以内质数的前缀和
\(p_j\)代表从小到大数第\(j\)个质数
\(g(n,j)\)表示\(n\)以内的质数以及最小质因子大于\(p_j\)的数的\(f\)的和
\(g(n,j)=\sum_{i=1}^{n}{f(i)[i \in prime | min(p) > p_j]}\)
可以发现这个东西就是埃氏筛法筛了\(j\)个质数后没被筛过的数的\(f\)加上质数的\(f\)
那么\(g(n,|P|)\)就表示\(\sum_{i=2}^{n}{f(i)[i\in prime]}\)\(|p|\)表示质数集合的大小
因为我们计算\(g\)的这一部分实质上是要只考虑质数的前缀和
所以在计算\(g\)的过程中的\(f(x)\)都是直接带入函数\(f\)对于质数情况下的多项式
所以我们可以快速求出递推的初始值\(g(n,0)\)
也就是把每个数都带入到对于质数情况下的多项式中的值的和

那么来考虑递推这个东西
考虑从\(p_{j-1}\)推到\(p_j\)有什么变化?
要去掉所有最小质因数为\(p_j\)的合数的贡献
那么就可以分情况讨论一下:

1.如果\(p_j^2 > n\) , 那么最小质因数为\(p_j\)的最小的合数也一定大于\(n\)了,所以\(g(n,j)=g(n,j-1)\)
2.如果\(p_j^2 \le n\), 那么我们就要减去最小质因数为\(p_j\)的所有合数的贡献了,
那么就是\(g(n,j)=g(n,j-1)-f(p_j)\times (g(\frac{n}{p_j} , j - 1) - \sum_{i=1}^{j-1}{f(p_i)})\)
解释一下这个式子:
因为\(p_j\)是最小的质因数,所以我们单独提出来\(p_j\),那么剩下的部分一定不会小于\(p_j\),所以后面直接乘上了\(g(\frac{n}{p_j} , j - 1)\)
但是直接这样做就把\(p_{j-1}\)以内的质数给筛掉了
所以我们要去掉那部分质数的贡献
所以就得到了那个递推式

这样我们就通过限制最小质因数求出了\(n\)以内的质数的\(f\)的前缀和
然后我们再将质数推广到全体的数
那么这时候的\(f\)所代表的就是真正的\(f\)值了
\(S(n,j)\)表示所有的最小质因子大于等于\(p_j\)\(f\)的和
也就是\(S(n,j)=\sum_{i=2}^{n}{f(i)[min(p)\ge p_j]}\)
那么我们考虑ta的递推
首先考虑质数
\(n\)以内的全体质数前缀和为\(g(n,|P|)\)
但是我们还要去掉前\(j-1\)个质数前缀和
所以质数的贡献就是\(g(n,|P|)-\sum_{i=1}^{j-1}{f(p_i)}\)
然后考虑非质数的贡献
那么我们就从\(j\)枚举这个数的最小质因数\(p\)
然后再枚举最小质因数的多少次幂\(p^c\)
那么根据积性函数的性质,\(f(x\times y)=f(x) \times f(y)[gcd(x , y)=1]\)
我们必须让\(p\)与要乘上的数互质,并且由于\(p\)是最小质因子,所以\(\frac{n}{p^c}\)的最小质因子必须要大于\(p_k\)
但是这样对于每一个枚举的\(p^c\),我们都把\(p^{c+1}\)给筛掉了,所以还要加回来
那么就是\(\sum_{k=j}^{p_k^2\le n}\sum_{t=1}^{p_{k}^{t+1}\le n}{f(p^c)\times S(\frac{n}{p_k^t} , k+1)+f(p_k^{t+1})}\)
总和起来就是\(S(n,j)=g(n,|p|)-\sum_{i=1}^{j-1}{f(p_i)}+\sum_{k=j}^{p_k^2\le n}\sum_{t=1}^{p_{k}^{t+1}\le n}{f(p^c)\times S(\frac{n}{p_k^t} , k+1)+f(p_k^{t+1})}\)

那么最后积性函数\(f\)的前缀和\(\sum_{i=1}^{n}f(i)=S(n,1)+f(1)\)

代码比较长,大致就长这样

inline void presolve(int n) { // 预处理出前根号个质数以及质数的相关前缀和 
    
}
int S(int x , int j) { // S(n,j)
    if(x <= 1 || pri[j] > x) return 0 ;
    int k = (x <= sz) ? id1[x] : id2[n / x] ; // x是哪个块
    int ret = g[k] - sum(j - 1) ;// 计算出小于k的第j个质数及以后的函数值的前缀和 
    for(int i = j ; i <= pnum && pri[i] * pri[i] <= x ; i ++)
        for(int t = 1 ; fpw(pri[i] , t + 1) <= x ; t ++)
            ret += f(fpw(pri[i] , t)) /*计算pri[i] ^ t的函数值(这时候是真正的函数值了)*/ * S(x / fpw(pri[i] , t) , i + 1) + f(fpw(pri[i] , t + 1)) /*因为前面筛的时候无法考虑到完全t+1次方数,所以要把这部分的函数值加上 */ ;
    return ret ;
}
inline int min_25(int n) {
    sz = sqrt(n) ; presolve(sz) ;
//  整除分块预处理需要的块,w[i]就表示块i对应的值 
    for(int l = 1 , r ; l <= n ; l = r + 1) {
        r = n / (n / l) ; w[++m] = n / l ;
        if(w[m] <= sz) id1[w[m]] = n / l ;
        else id2[n / w[m]] = m ;
        g[m] = /* 快速计算出把w[m]当做质数的函数值, */
    }
    for(int j = 1 ; j <= pnum ; j ++) // 分层的枚举质数 
        for(int i = 1 ; i <= m && 1LL * pri[j] * pri[j] <= w[i] ; i ++) { // 从大到小枚举数 
            int k = (w[i] / pri[j] <= sz) ? id1[w[i] / pri[j]] : id2[n / (w[i] / pri[j])] ; 
            g[i] -= f(pri[j]) /*pri[j]的函数值*/ * (g[k] - sum(j - 1) /*前j-1个质数的函数值的和*/)
        }
    return S(n , 1) + 1 ; // 计算出S(n,1) + f(1) 
}

猜你喜欢

转载自www.cnblogs.com/beretty/p/10381541.html