杜教筛

积性函数:

对于任意互质的整数 \(a,b\)\(f(ab)=f(a)f(b)\)

  • 常见的积性函数:\(\varphi,\mu,\sigma,d\)
  • 常见的完全积性函数:\(\epsilon,I,id\)

其中\(\epsilon(n)=[n=1],I(n)=1,id(n)=n\)

狄利克雷卷积:

\(f, g\) 是两个数论函数,它们的狄利克雷卷积卷积是:\((f*g)(n)=\sum_{d|n}f(d)g(\frac{n}{d})\)
性质:

  • \(\mu*I=\epsilon\)
  • \(\varphi*I=id\)
  • \(\mu*id=\varphi\)
  • \(I*I=d \ [d(n)=\sum_{i|n}1]\)
  • \(id*I=\sigma\ [\sigma(n)=\sum_{i|n}i]\)

杜教筛:

设现在要求积性函数 \(f\) 的前缀和:\(\sum_{i=1}^nf(n)=S(n)\)
再找一个积性函数\(g\),则考虑它们的狄利克雷卷积的前缀和:

\[\begin{aligned} &\sum_{i=1}^n(f*g)\\ =&\sum_{i=1}^n\sum_{d|i}f(d)g(\frac{i}{d})\\ =&\sum_{d=1}g(d)\sum_{i=1}^{\frac{n}{d}}f(i)\\ =&\sum_{d=1}g(d)S(\lfloor\frac{n}{d}\rfloor) \end{aligned} \]

通过一系列运算,我们可以得到杜教筛的核心公式:

\[S(n)=g(1)S(n)=\sum_{i=1}^n(f*g)-\sum_{i=2}g(i)S(\lfloor\frac{n}{i}\rfloor) \]

所以我们只要构造出\(g\)能够快速求得\(\sum_{i=1}^n(f*g)\)\(g\)的前缀和,就能用数论分块递归求解。
我们只要先打表打出\(n^{\frac{2}{3}}\)的答案,中间的结果也可以用unordered_map记忆化,然后再利用递归求解。那么复杂度为\(O(n^{\frac{2}{3}})\)

ll Sphi(int n){
    if(n <= N) return phi[n];	//打表打出了
    if(mphi.count(n)) return mphi[n];	//记忆化
    ll ans = ...
    for(int l = 2, r; l <= n; l = r + 1){
        r = n / (n / l);
        //ans ...
    }
    return mphi[n] = ans;
}

考虑到上面的求和过程中出现的都是 \(\lfloor \frac{n}{i} \rfloor\) 。开一个大小为两倍 \(\sqrt n\)n的数组 \(dp\) 记录答案。如果现在需要求出 \(GetSum(x)\) ,若 \(x \leq \sqrt n\)​ ,返回 \(dp[x]\),否则返回 \(dp[\sqrt n + n / i]\)即可。这样可以省去哈希表的复杂度。

这种做法可以把未打表的值直接映射出来

int n, up;
int N = 6000000;
ll mphi[100000];    //保存中间值
ll getans(int x){	//已经打表或记录中间值
    return x <= N? mu[x] : mmu[up / x];
}
void solve(int n){
    int t = up / n;
    if(n <= N) return;
    if(vis2[t]) return; //已经hash出来了
    vis2[t] = 1;
    mphi[t] = ...;  //初始值
    for(int l = 2, r; l <= n; l = r + 1){
        r = n / (n / l);
        solve(n / l);   //开始记录
        mphi[t] -= (r - l + 1) * getans(n / l);
    }
}
int main(){
    init(N);
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        up = n;
        if(n <= N) printf("%lld\n", phi[n]);    //预处理的
        else{
            memset(vis2, 0, sizeof(vis2));
            solve(n);
            printf("%lld\n", mphi[1]);  //最终答案为第1个
        }
    }
    return 0;
}

from:浅谈杜教筛

posted @ 2019-09-04 20:29  KirinSB  阅读(120)  评论(0编辑  收藏  举报