1. 程式人生 > 實用技巧 >Luogu 2257 - YY的GCD (莫比烏斯反演、分塊)

Luogu 2257 - YY的GCD (莫比烏斯反演、分塊)

Luogu 2257 - YY的GCD


題意

求下式的值

\[\sum_{p\in prime}\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=p] \]


限制

\(Case=10^4,\ n,m\leq 10^7\)



思路

\(n\leq m\)

根據\(gcd(i,j)=k\iff gcd(\frac i k,\frac j k)=1\),化簡上式可得

\[\sum_{p\in prime}\sum_{i=1}^{\lfloor\frac n p\rfloor}\sum_{j=1}^{\lfloor\frac m p\rfloor}[\gcd(i,j)=1] \]

由莫比烏斯反演得

\[[\gcd(i,j)=1]=\sum_{d|\gcd(i,j)}\mu(d) \]

所以原式可得

\[\sum_{p\in prime}\sum_{i=1}^{\lfloor\frac n p\rfloor}\sum_{j=1}^{\lfloor\frac m p\rfloor}\sum_{d|\gcd(i,j)}\mu(d) \]

轉化為列舉\(\gcd\)的因子\(d\),由於\(i,j\)一定是\(d\)的倍數,所以不妨將\(i,j\)縮小\(d\)

\(f(i,j)=\sum_{d|\gcd(i,j)}\mu(d)\),可以知道對於某個\(\mu(d)\),需要累加的次數等同於\(i\)

範圍內\(d\)倍數的個數與\(j\)範圍內\(d\)倍數的個數之積

又已知在\(1\)~\(a\)範圍內\(b\)的倍數有\(\lfloor\frac a b\rfloor\)

所以原式便等同於

\[&\sum_{p\in prime}\sum_{d=1}^{\lfloor\frac n p\rfloor}\mu(d)\lfloor\frac{\lfloor\frac n p\rfloor}d\rfloor\lfloor\frac{\lfloor\frac m p\rfloor}d\rfloor\\ =&\sum_{p\in prime}\sum_{d=1}^{\lfloor\frac n p\rfloor}\mu(d)\lfloor\frac n {pd}\rfloor\lfloor\frac m {pd}\rfloor \]

此時直接處理複雜度是\(O(n\sqrt n)\)

但我們可以先設\(x=pd\),將其看作一個整體進行列舉

那麼原式將會變成

\[&\sum_{p\in prime}\sum_{d=1}^{\lfloor\frac n p\rfloor}\mu(\lfloor\frac x p\rfloor)\lfloor\frac n x\rfloor\lfloor\frac m x\rfloor\\ =&\sum_{x=1}^n\lfloor\frac n x\rfloor\lfloor\frac m x\rfloor\sum_{p\in prime,\ p|x}\mu(\lfloor\frac x p \rfloor) \]

於是對於\(\sum_{p\in prime,\ p|x}\mu(\lfloor\frac x p \rfloor)\),可以在求解莫比烏斯函式的線性篩裡順帶求出

再對其求次字首和,對原式進行分塊求解,即可在\(O(\sqrt n)\)內求出答案



程式碼

Case Max (2840ms/4000ms)

O2 Case Max(2040ms/4000ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10000000;

int primcnt;
ll mu[N+50],prim[N+50];
ll f[N+50];
bool vis[N+50];

void init(int n)
{
    memset(vis,false,sizeof vis);
    mu[1]=1;
    primcnt=0;
    for(int i=2;i<=n;i++)
    {
        if(!vis[i])
        {
            prim[primcnt++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<primcnt;j++)
        {
            int k=i*prim[j];
            if(k>n)
                break;
            vis[k]=true;
            if(i%prim[j]==0)
            {
                mu[k]=0;
                break;
            }
            else
                mu[k]=-mu[i];
        }
    }
    for(int j=0;j<primcnt;j++)
        for(int i=1;i*prim[j]<=n;i++)
            f[i*prim[j]]+=mu[i];
    for(int i=2;i<=n;i++) //轉為字首和
        f[i]+=f[i-1];
}

void solve()
{
    int n,m;
    cin>>n>>m;
    if(n>m)
        swap(n,m);
    ll ans=0;
    for(int i=1;i<=n;)
    {
        int nxt=min(n/(n/i),m/(m/i));
        ans+=(f[nxt]-f[i-1])*(n/i)*(m/i);
        i=nxt+1; //分塊跳轉
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    init(N);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}