zoukankan      html  css  js  c++  java
  • 杜教筛学习笔记

    杜教筛的作用是可以快速求出积性函数的前缀和,相比于传统的线性筛,杜教筛可以在(O(n^{2/3}))的时间求出积性函数的前缀和。

    PS:一下所有除法运算为向下取整(不知道向下取整的LaTex)

    前置芝士1:积性函数

    什么是积性函数呢?若a⊥b(a,b互质),那么有(f(ab)=f(a)f(b)) 则称(f(x))的积性函数。

    前置芝士2:狄利克雷卷积

    定义两个积性函数的狄利克雷卷积为((f*g)(i)=sum_{d|n}f(d))*$g(frac{d}{n}) $

    给出几个常见的狄利克雷卷积的结论

    ((I(x)=1,id(x)=x,epsilon(x)=[x==1]))

    1.(mu*I=epsilon)

    证明:(mu*I=sum_{d|n}mu(d)*I(frac{n}{d})=sum_{d|n}mu(d)=[n==1]=epsilon)

    倒数第二步是莫比乌斯函数的一个结论

    2.(φ*I=id(n))

    证明:(φ*I=sum_{d|n}φ(d)*I(frac{n}{d})=sum_{d|n}φ(d)=n=id(n))

    对于倒数第二步,考虑统计与(n)(gcd)分别为(1,2,...,n)的过程:

    对于(1-n)所有数,与(n)(gcd)(1)的有(φ(n))个;若(2|n),则与(n)(gcd)为2有(φ(frac{n}{2}))个……以此类推,总共有(n)个数,故(sum_{d|n}φ(d)=n)

    3.(id*mu=φ(n))

    证明:
    (φ(n)*I*mu=id*mu)----------结论2可知

    (φ(n)*epsilon=id*mu)-------------结论1可知

    (φ(n)=id*mu)----------------把卷积拆开即可

    前置芝士3:莫比乌斯反演

    (f=g*I),则有(g=f*mu)

    我们把卷积拆开

    原式为:若(f(n)=sum_{d|n}g(d)),则有(g(n)=sum_{d|n}f(frac{n}{d})*mu(d))

    证明:(f*mu=g*I*mu=g*epsilon=g)

    所以(1)的逆是(mu)

    正文:杜教筛

    我们构造积性函数f,g,令(S(n)=sum_{i=1}^{n}f(i))

    (sum_{i=1}^{n}(f*g)(i)=sum_{i=1}^{n}sum_{d|n}f(d)*g(frac{n}{d})=sum_{i=1}^{n}g(i)*sum_{j=1}^{[frac{n}{i}]}f(j)=sum_{i=1}^{n}g(i)*S(frac{n}{i}))

    移向得:(g(1)*S(n)=sum_{i=1}^{n}(f*g)(i)-sum_{i=2}^{n}g(i)*S(frac{n}{i}))

    (S(n)=frac{sum_{i=1}^{n}(f*g)(i)-sum_{i=2}^{n}g(i)*S(frac{n}{i})}{g(1)})//以后称此为杜教筛公式

    我们发现,S(n)是我们所求,我们把S(n)拆成了递归形式,假设我们可以求出(sum_{i=1}^{n}(f*g)(i))(g(x)),我们就可以对(sum_{i=2}^{n}g(i)*S(frac{n}{i}))进行整除分块,从而递归求出(S(N))

    看懂了上述证明,那本题就很简单了吧。

    一:(sum_{i=1}^{n}mu(i))

    因为(mu*I=epsilon) ,所以我们定义(g=I),带入杜教筛公式得(S(n)=1-sum_{i=2}^{n}S(frac{n}{i})),然后就是整除分块的模板了

    二:(sum_{i=1}^{n}φ(i))

    因为(φ*I=id(n))(其实也可以用(φ(n)=id*mu)),所以我们还是定义(g=I),带入杜教筛公式得(S(n)=frac{(1+x)*x}{2}-sum_{i=2}^{n}S(frac{n}{i})),然后发现和上面区别并不大

    这样直接求显然是不能做到非线性的,所以我们需要先预处理出一部分的(φ(i),mu(i)),然后对于我们处理过的答案,我们可以用一个map来存储(相当于记忆化搜索)以优化时间复杂度。

    经过理论证明,当预处理的值=(n^{frac{2}{3}})时,时间复杂度为(O(n^{frac{2}{3}}))

    注意几点:

    1.本题卡常,所以不要全部开longlong,也不要用map,而用c++11的STL:unordered_map(少了map的排序,复杂度不带log,但常数仍然很大,而且本地要开c++11才能过编译)

    2.注意杜教筛的公式,(sum_{i=2}^{n}g(i)*S(frac{n}{i}))是从2开始的,不然会进入死递归(我也不知道叫什么)

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define il inline
    #define re register
    #define debug printf("Now is Line : %d
    ",__LINE__)
    #define file(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
    #define ll long long
    #define mod 1000000007
    il int read()
    {
        re int x=0,f=1;re char c=getchar();
        while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
        while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
        return x*f;
    }
    #define maxn 5000000
    ll pai[maxn+5];
    int prim[maxn+5],cnt,mu[maxn+5];
    bool vis[maxn+5];
    unordered_map<int,int> ans_mu;
    unordered_map<int,ll> ans_pai;
    il void init()
    {
        mu[1]=pai[1]=1;
        for(re int i=2;i<=maxn;++i)
        {
            if(!vis[i]) prim[++cnt]=i,mu[i]=-1,pai[i]=i-1;
            for(re int j=1;j<=cnt&&prim[j]*i<=maxn;++j)
            {
                vis[prim[j]*i]=1;
                if(i%prim[j]==0)
                {
                    pai[i*prim[j]]=pai[i]*prim[j];
                    break;
                }
                pai[i*prim[j]]=pai[prim[j]]*pai[i];
                mu[i*prim[j]]=-mu[i];
            }
        }
        for(re int i=1;i<=maxn;++i) mu[i]+=mu[i-1],pai[i]+=pai[i-1];
    }
    il ll get_pai(ll x)
    {
        if(x<=maxn) return pai[x];
        if(ans_pai[x]) return ans_pai[x];
        ll ans=((1ll+x)*x)/2ll;
        for(re int l=2,r;l<=x;l=r+1)//其实这里可能会爆int,可以改用用unsigned int
        {
            r=x/(x/l);
            ans-=1ll*(r-l+1)*get_pai(x/l);
        }
        return ans_pai[x]=ans;
    }
    il int get_mu(int x)
    {
        if(x<=maxn) return mu[x];
        if(ans_mu[x]) return ans_mu[x];
        int ans=1;
        for(re int l=2,r;l<=x;l=r+1)
        {
            r=x/(x/l);
            ans-=(r-l+1)*get_mu(x/l);
        }
        return ans_mu[x]=ans;
    }
    il void doit()
    {
        int T=read();
        while(T--)
        {
            int x=read();
            printf("%lld %d
    ",get_pai(x),get_mu(x));
        }
    }
    signed main()
    {
        init(),doit();
        return 0;
    }
    
  • 相关阅读:
    LeetCode: Next Permutation 解题报告
    LeetCode: Subsets 解题报告
    LeetCode: Recover Binary Search Tree 解题报告
    LeetCode: Find Peak Element 解题报告
    LeetCode: Valid Parentheses 解题报告
    LeetCode: First Missing Positive 解题报告
    LeetCode: Best Time to Buy and Sell Stock III 解题报告
    Nginx系列(二)——流量分发管控
    Nginx系列(一)——HTTP/TCP/UDP负载均衡
    运维电子书PDF汇总
  • 原文地址:https://www.cnblogs.com/bcoier/p/10362913.html
Copyright © 2011-2022 走看看