有一个M * N的表格,行与列分别是1 - M和1 - N,格子中间写着行与列的最大公约数Gcd(i, j)(1 <= i <= M, 1 <= j <= N)。
例如:M = 5, n = 4。
1 2 3 4 5
1 1 1 1 1 1
2 1 2 1 2 1
3 1 1 3 1 1
4 1 2 1 4 1
给出M和N,求这张表中有多少个质数。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 1000)
第2 - T + 1行:每行2个数M,N,中间用空格分隔,表示表格的宽和高。(1 <= M, N <= 5 * 10^6)
Output
共T行,每行1个数,表示表格中质数的数量。
Input示例
2
10 10
100 100
Output示例
30
2791
分析:
容易想到是枚举质数p,然后推导公式。然而就是这么做。
推导:
默认n<=m
[s]表示如果s为真,则表达是为1,[p]表示p为质数是为1,否则为0;
ANS(m,n)=∑p∑i=1⌊np⌋∑j=1⌊mp⌋[(i,j)=1][p]=∑p∑i=1⌊np⌋∑j=1⌊mp⌋∑k=1μ(k)[k|i,k|j][p]=∑p∑k=1⌊np⌋∑i=1⌊nkp⌋∑j=1⌊mkp⌋μ(k)[p]=∑k=1n∑p=1⌊nk⌋⌊nkp⌋⌊mkp⌋μ(k)[p]=∑k=1n∑p|k⌊nk⌋⌊mk⌋μ(kp)[p]=∑k=1n⌊nk⌋⌊mk⌋∑p|kμ(kp)[p]=∑k=1n⌊nk⌋⌊mk⌋f(k)
f(k) 可以打表,分析一下 f 的一些性质:
令 k=∏si=1pkii
如 ki 中一个大于2或者有两个大于1,那么f(k)=0;
理解起来也比较直观。接着就可以递推。
/*************************************************************************
> File Name: 1192.cpp
> Author: kelvin
> Mail: [email protected]
> Created Time: 2016年05月18日 星期三 10时49分45秒
************************************************************************/
#include<bits/stdc++.h>
using namespace std;
#define REP(i,a,b) for(int i=a;i<b;++i)
#define LL long long
#define mset(a,b) memset(a,b,sizeof a)
#define scan(n) scanf("%d",&n)
const int maxn = ;
const int mod = ;
int prime[maxn];
LL f[maxn];
int mu[maxn];
void getP()
{
mu[]=;
REP(i,,maxn){
if(!prime[i]){
prime[++prime[]]=i;
mu[i]=-;
f[i]=;
}
for(int j=;j<=prime[] && (LL)prime[j]*i<maxn;++j){
prime[i*prime[j]]=;
if(i%prime[j])
{
f[i*prime[j]]=mu[i]-f[i];
mu[i*prime[j]]=-mu[i];
}
else{
f[i*prime[j]]=mu[i];
break;
}
}
}
REP(i,,maxn) f[i]+=f[i-];
}
LL getans(int n,int m)
{
LL ret=;
for(int d=,j;d<=n;d=j+){
j=min(m/(m/d),n/(n/d));
ret+=(LL)(n/d)*(m/d)*(f[j]-f[d-]);
}
return ret;
}
int main()
{
getP();
int t;
scan(t);
int m,n;
while(t--){
scanf("%d%d",&m,&n);
if(n>m) swap(n,m);
LL ans=getans(n,m);
cout<<ans<<endl;
}
return ;
}