1. 程式人生 > >洛谷P2398 GCD SUM (數學)

洛谷P2398 GCD SUM (數學)

洛谷P2398 GCD SUM

題目描述

for i=1 to n

for j=1 to n

 sum+=gcd(i,j)

給出n求sum. gcd(x,y)表示x,y的最大公約數.

輸入輸出格式

輸入格式:

n

輸出格式:

sum

輸入輸出樣例

輸入樣例#1:

2

輸出樣例#1:

5

說明

資料範圍 30% n<=3000 60% 7000<=n<=7100 100% n<=100000

Solution

這道題的做法貌似很多...如果你同時會狄利克雷卷積和莫比烏斯反演的話也可以強行反演一波,反正蒟蒻我是不會卷的,所以在這裡介紹另外一種做法

一個式子描述題意\(ans=\sum _{i=1}^{n}\sum_{j=1}^{n}gcd(i,j)\)

直接暴力肯定是不行的,我們想一下有沒有辦法求出一個數它作為\(gcd\)的貢獻呢?

對於兩個數\(gcd(a,b)=1\to gcd(ka,kb)=k(ka<=n,kb<=n)\),所以k作為\(gcd\)的貢獻就是\(gcd(x,y)=k\)的數對的對數,還不準確,因為數對\((x,y),(y,x)\),分別對答案都有貢獻,但x=y的情況只能算一次,所以是 **數對的個數*2-1**,那麼關鍵就在於怎麼快速算出這個對數

我們發現\(n\)以內\(gcd\)\(k\)的對數,實際上就是\(\lfloor\frac{n}{k}\rfloor\)以內gcd為1的數對的對數,這其實就是\(\lfloor\frac{n}{k}\rfloor\)

以內每個數的尤拉函式的值之和,即\(2\times \sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\phi(i)-1\),這個對數*數值就是每個數的貢獻

線性篩一遍尤拉函式求字首和就可以了....

Code

#include<bits/stdc++.h>
#define in(i) (i=read())
#define il extern inline
#define rg register
#define mid ((l+r)>>1)
#define Min(a,b) ((a)<(b)?(a):(b))
#define Max(a,b) ((a)>(b)?(a):(b))
#define lol long long
using namespace std;

const lol N=1e5+10;

lol read() {
    lol ans=0, f=1; char i=getchar();
    while (i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
    while (i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+(i^48), i=getchar();
    return ans*f;
}

lol n,ans,cnt,vis[N],prime[N],phi[N]={0,1};

void init() {
    for (lol i=2;i<=N-10;i++) {
        if (!vis[i]) prime[++cnt]=i,phi[i]=i-1;
        for (lol j=1;j<=cnt && prime[j]*i<=N-10;j++) {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j]; break;}
            else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }for (lol i=1;i<=N-10;i++) phi[i]+=phi[i-1];
}

int main()
{
    in(n); init();
    for (lol i=1;i<=n;i++) ans+=(2*phi[n/i]-1)*i;
    cout<<ans<<endl;
}