1. 程式人生 > >Gcd HYSBZ - 2818 (莫比烏斯反演)

Gcd HYSBZ - 2818 (莫比烏斯反演)

Gcd

 HYSBZ - 2818 

給定整數N,求1<=x,y<=N且Gcd(x,y)為素數的
數對(x,y)有多少對.

 

Input

一個整數N

Output

如題

Sample Input

4

Sample Output

4

Hint

 

hint

對於樣例(2,2),(2,4),(3,3),(4,2)

 

1<=N<=10^7

 

 

推導:

本題的本質就是

\sum_{d=1}^{N}[d\in P]\sum_{i=1}^{N}\sum_{j=1}^{N}[gcd(i,j)=d]

後面那個式子我們在莫比烏斯反演中已經見過很多了。

f(x)=\sum_{i=1}^{N}\sum_{j=1}^{N}[gcd(i,j)=x]

則有F(x)=\sum_{x|d}f(d),進一步對F(x)進行推導得到:

F(x)=\sum_{i=1}^{N}\sum_{j=1}^{N}[x|gcd(i,j)]=\sum_{i=1}^{N/x}\sum_{j=1}^{N/x}[1|gcd(i,j)]=floor(\frac{N}{x})*floor(\frac{N}{x})

對F(x)反演推回

f(x)=\sum_{x|d}\mu(\frac{d}{x})F(d)=\sum_{k=1}^{N/x}\mu(k)*F(kx)

則最初的式子就變成了

\sum_{d=1}^{N}[d\in P]\sum_{k=1}^{N/x}\mu(k)*F(kx)

複雜度論證:

預處理mu[],F[],check[],以及最終處理ans都是O(1)的複雜度

主要是處理f(x)的複雜度的。

對於f(x)的處理,我們採用這樣一段程式碼:

for(int i=1;i<=n;i++)
	{
		if(check[i])continue;
		for(int j=i;j<=n;j+=i)
		{
			f[i]+=1ll*mu[j/i]*F[j];
		}
	}

其中的i就是x,j就是kx,可以看出來這個式子的複雜度是

N*(1+\frac{1}{2}+\frac{1}{3}+.....+\frac{1}{N-1}+\frac{1}{N})

這個式子求和較難,不如將它放大一點,成:

N*\int_{1}^{N}\frac{1}{x}dx=N*ln(N)

這個複雜度沒有問題

AC程式碼:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN=1e7;
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10];
void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    check[1]=true;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++)
    {
        if( !check[i] ){
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j++)
        {
            if(i * prime[j] > MAXN) break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0){
                mu[i * prime[j]] = 0;
                break;
            }else{
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
long long F[MAXN+10],f[MAXN+10];
int main()
{
	Moblus();
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) F[i]=1ll*(n/i)*(n/i);
	for(int i=1;i<=n;i++)
	{
		if(check[i])continue;
		for(int j=i;j<=n;j+=i)
		{
			f[i]+=1ll*mu[j/i]*F[j];
		}
	}
	long long ans=0;
	for(int i=1;i<=n;i++)if(!check[i]) ans+=1ll*f[i];
	printf("%lld\n",ans);
}