1. 程式人生 > 實用技巧 >BZOJ-3944 Sum(杜教篩模板)

BZOJ-3944 Sum(杜教篩模板)

題目描述

  求 \(S_1(n)=\displaystyle\sum_{i=1}^{n}\varphi(i)\)\(S_2(n)=\displaystyle\sum\limits_{i=1}^{n}\mu(i)\) 的值,\(T\leq 10,n<2^{31}-1\)

杜教篩

  在莫比烏斯反演的題目中,往往要求出一些數論函式的字首和,利用 杜教篩 可以在低於線性時間的複雜度內求出這些字首和。

  對於數論函式 \(h=f\ast g\),要求我們計算 \(\displaystyle\sum\limits_{i=1}^{n}h(i)\)

  記 \(S(n)=\displaystyle\sum_{i=1}^{n}f(i)\)

  想辦法構造一個 \(S(n)\) 關於 \(S(\lfloor\frac{n}{i}\rfloor)\) 的遞推式。

\[\sum\limits_{i=1}^{n}h(i)=\sum\limits_{i=1}^{n}\sum_{d|i}f(d)·g(\frac{i}{d})=\sum\limits_{d=1}^{n}g(d)·\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)=\sum\limits_{d=1}^{n}g(d)·S\Big(\Big\lfloor\frac{n}{d}\Big\rfloor\Big) \]

證明:

  \(f(d)g(\frac{i}{d})\)

就是對所有 \(i\leq n\) 算貢獻,因此變換列舉順序,列舉 \(d,\frac{i}{d}\)(分別對應新的 \(i,j\))。

\[\begin{aligned}&\displaystyle\sum\limits_{i=1}^{n}\sum\limits_{d|i}f(d)g(\frac{i}{d})\\=&\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{\lfloor\frac{n}{i}\rfloor}g(i)f(j)\\=&\sum\limits_{i=1}^{n}g(i)\sum\limits_{j=1}^{\lfloor\frac{n}{i}\rfloor}f(j)\\=&\sum\limits_{i=1}^{n}g(i)S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big) \end{aligned} \]

  可以得到遞推式:

\[S(n)=g(1)S(n)=\sum\limits_{i=1}^{n}(f\ast g)(i)-\sum\limits_{i=2}^{n}g(i)S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big) \]

  假如我們可以快速對 \(\displaystyle\sum\limits_{i=1}^{n}(f\ast g)(i)\) 求和,並用數論分塊求解 \(\displaystyle\sum\limits_{i=2}^{n}g(i)S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big)\) 就可以在較短時間內求得 \(g(1)S(n)\)

莫比烏斯函式字首和

  由狄利克雷卷積,我們知道:

\[\epsilon=\mu\ast I\Longleftrightarrow\epsilon(n)=[n=1]\Longleftrightarrow\epsilon(n)=\sum\limits_{d|n}\mu(d)\\S_2(n)=\sum\limits_{i=1}^{n}\epsilon(i)-\sum\limits_{i=2}^{n}S_1\big(\big\lfloor\frac{n}{i}\big\rfloor\big) \]

  由於 \(\lfloor\frac{n}{i}\rfloor\) 最多隻有 \(O(\sqrt{n})\) 種取值,可以用數論分塊來計算每一項的值。

  直接計算的時間複雜度為 \(O(n^{\frac{3}{4}})\)。考慮先線性篩預處理出前 \(n^{\frac{2}{3}}\) 項,剩餘部分的時間複雜度為 \(O\Big(\displaystyle\int_{0}^{n^{\frac{1}{3}}}\sqrt{\frac{n}{x}}dx\Big)=O(n^{\frac{2}{3}})\)

  對於較大的值,需要用 \(\text{map}\) 存下其對應的值,方便以後使用時直接使用之前計算的結果。

尤拉函式字首和

使用杜教篩求解

  記 \(S_1(i)=\displaystyle\sum\limits_{i=1}^{n}\varphi(i)\)

  由於 \(\varphi \ast I=\mathbf{id}\),所以:

\[\displaystyle\sum\limits_{i=1}^{n}(\varphi\ast I)(i)=\sum\limits_{i=1}^{n}I(i)· S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big)\\\sum\limits_{i=1}^{n}\mathbf{id}(i)=\sum\limits_{i=1}^{n}I(i)·S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big)\\\frac{n(n+1)}{2}=\sum\limits_{i=1}^{n}S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big)\\S_1(n)=\sum_{i=1}^{n}\mathbf{id}(i)-\sum\limits_{i=2}^{n}I(i)S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big)=\frac{n(n+1)}{2}-\sum\limits_{i=2}^{n}S\Big(\Big\lfloor\frac{n}{i}\Big\rfloor\Big) \]

使用莫比烏斯反演求解

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(i,j)=1]=\sum\limits_{d=1}^{n}\mu(d)\lfloor\frac{n}{d}\rfloor^{2} \]

  由於題目所求的是 \(\displaystyle\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i}1·[\gcd(i,j)=1]\),所以我們排除掉 \(i=1,j=1\) 的情況,並將結果除以 \(2\) 即可。

  觀察到,只需求出莫比烏斯函式的字首和,就可以快速計算出尤拉函式的字首和了。時間複雜度 \(O(n^{\frac{2}{3}})\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=2000010;//n^(2/3)
bool vis[N];
long long n,prime[N+10],mu[N+10],sum[N+10],cnt;
map<long long,long long> mp;
long long S_mu(long long n)
{
    if(n<N)
        return sum[n];
    if(mp[n])
        return mp[n];
    long long ans=1;
    for(long long l=2,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans=ans-S_mu(n/l)*(r-l+1);
    }
    mp[n]=ans;
    return ans;
}
long long S_phi(long long n)
{
    long long ans=0;
    for(long long l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans=ans+(S_mu(r)-S_mu(l-1))*(n/l)*(n/l);
    }
    return (ans-1)/2+1;
}
void init()
{
    mu[1]=1;
    for(int i=2;i<=N;i++)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
                break;
            else
                mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i=1;i<=N;i++)
        sum[i]=sum[i-1]+mu[i];
}
int main()
{
    init();
    int T;
    cin>>T;
    while(T--)
    {
        scanf("%lld",&n);
        printf("%lld %lld\n",S_phi(n),S_mu(n));
    }
    return 0;
}