YY的GCD

题目描述

神犇YY虐完数论后给傻×kAc出了一题

给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对

kAc这种傻×必然不会了,于是向你来请教……

多组输入

输入输出格式

输入格式:

第一行一个整数T 表述数据组数

接下来T行,每行两个正整数,表示N, M

输出格式:

T行,每行一个整数表示第i组数据的结果

输入输出样例

输入样例#1:

2
10 10
100 100

输出样例#1:

30
2791

说明

T = 10000

N, M <= 10000000


好像是莫比乌斯反演的一道入门题

但是我也不会,然后对着题解抄了好久

题目要求\(\sum_{i=1}^{n}\sum_{j=1}^{m}{gcd(i,j)∈prime}\)

所以设目标函数\(f(n) = \sum_{i=1}^{n}\sum_{j=1}^{m}{[gcd(i,j)=n]}\)

然后再设\(F(n) = \sum_{n|d}{f(d)}\)

F(n)代表的是gcd是n及n的倍数的数目

所以 \(Ans = \sum_{p∈prime}\sum_{i=1}^{n}\sum_{j=1}^{m}{gcd(i,j)=p}\)

\(Ans = \sum_{p∈prime}f(p)\)

然后我们对f(p)套用反演公式

\(Ans = \sum_{p∈prime}\sum_{p|d}{μ(\frac{d}{p})F(d)}\)

设i = \(\frac{d}{p}\)

\(Ans = \sum_{p∈prime}\sum_{i=1}^{min(n/p,m/p)}{μ(i)F(i*p)}\)

再设\(k = i * p\)

然后我们就只需要枚举k就好辣

\(Ans = \sum_{k=1}^{min(n,m)}\sum_{p|k}{μ(k/p)*F(k)}\)

然后\(F(k) = floor(n/k) * floor(m/k)\)

所以再对上个式子提公因式

把F(k)提出来

\(Ans = \sum_{k=1}^{min(n,m)}{floor(n/k) * floor(m/k)}\sum_{p|k}{μ(k/p)}\)

然而这样还是不能通过此题

所以要整除分块

算出每个μ(i)对每个位置的贡献(即μ(i)*prime)

然后记录一下\(μ\)函数的前缀和

就可以\(O(\sqrt{n})\)做了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
# define LL long long
const int M = 10000005 ;
using namespace std ;
inline int read() {
	char c = getchar() ; int x = 0 , w = 1 ;
	while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
	while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; }
	return x*w ;
}
bool Notp[M] ;
int pnum , p[M] , mu[M] ;
int f[M] ;
LL sum[M] ;
inline void Get_Mu(int n) {
	Notp[1] = true ; mu[1] = 1 ;
	for(int i = 2 ; i <= n ; i ++) {
		if(!Notp[i]) p[++pnum] = i , mu[i] = -1 ;
		for(int j = 1 ; j <= pnum && i * p[j] <= n ; j ++) {
			Notp[i * p[j]] = true ;
			if(i % p[j] == 0) {
				mu[i * p[j]] = 0 ;
				break ;
			}
			mu[i * p[j]] = -mu[i] ;
		}
	}
	for(int i = 1 ; i <= pnum ; i ++)
	  for(int j = 1 ; p[i] * j <= n ; j ++)
	    f[p[i] * j] += mu[j] ;
	for(int i = 1 ; i <= n ; i ++) sum[i] = sum[i - 1] + f[i] ;
}

int main() {
	Get_Mu(10000000) ;
	int T = read() , n , m ;
	LL Ans ;
	while(T--) {
		Ans = 0 ;
		n = read() ; m = read() ;
		if(n > m) swap(n , m) ;
		for(int l = 1 , r ; l <= n ; l = r + 1) {
			r = min( n / (n / l) , m / (m / l) ) ;
			Ans += (LL)(n / l) * (m / l) * (sum[r] - sum[l - 1]) ;
		}
		printf("%lld\n",Ans) ;
	}
	return 0 ;
}
posted @ 2018-09-02 10:26  beretty  阅读(174)  评论(0编辑  收藏  举报