Description
对于给出的 n 个询问,每次求有多少个数对
(x,y) ,满足 a≤x≤b,c≤y≤d ,且 gcd(x,y)=k , gcd(x,y) 函数为 x 和y 的最大公约数。
Input
第一行一个整数 n ,接下来
n 行每行五个整数,分别表示 a、b、c、d、k
Output
共 n 行,每行一个整数表示满足要求的数对
(x,y) 的个数
Sample Input
2
2 5 1 5 1
1 5 1 5 2
Sample Output
14
3
思路
首先我们可以利用容斥原理将一个询问分解为四个,每次询问有多少个数对 (x,y) 满足 1<=x<=n,1<=y<=m 且 gcd(x,y)=k 。
然后问题便等价于询问有多少个数对
(x,y)
满足
1<=x<=⌊nk⌋,1<=y<=⌊mk⌋
且
x
与
PS:这道题目的时间限制有 50s ,不过也千万别想枚举过掉它,因为 O(n3) 的复杂度在 n=1e5 的情况下可能要跑好久好久。
我们考虑使用莫比乌斯反演
因为之前的结论,我们可以令 f(i) 为 1<=x<=n,1<=y<=m 且 gcd(x,y)=i 的数对 (x,y) 的个数, F(i) 为 1<=x<=n,1<=y<=m 且 i|gcd(x,y) 的数对 (x,y) 的个数,满足 F(i)=∑i|df(d) 。
显然,我们有 F(i)=⌊ni⌋⌊mi⌋
反演之后即 f(i)=∑i|dμ(di)F(d)=∑i|dμ(di)⌊nd⌋⌊md⌋
枚举原题中
k
的倍数,我们就可以在
我们发现,对于其中连续的
s
项,有可能有
观察式子,我们发现
PS:关于如何求出二元组
(nx,mx)
取值范围相同的一段:对于当前点
x
,其向右延伸最远为
AC 代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 1e5+10;
typedef long long LL;
bool check[maxn];
int prime[maxn];
int mu[maxn];
LL sum[maxn];
void Moblus()
{
memset(check,false,sizeof(check));
mu[1]=1;
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 && i*prime[j]<maxn; j++)
{
int num=i*prime[j];
check[num]=true;
if(i%prime[j]==0)
{
mu[num]=0;
break;
}
else
{
mu[num]=-mu[i];
}
}
}
}
LL solve(int n,int m)
{
LL ans=0;
if(n>m)swap(n,m);
for(int i=1,la=0; i<=n; i=la+1)
{
la=min(n/(n/i),m/(m/i));
ans+=(sum[la]-sum[i-1])*(m/i)*(n/i);
}
return ans;
}
int main()
{
Moblus();
for(int i=1; i<maxn; i++)
sum[i]=sum[i-1]+mu[i];
int a,b,c,d,k,T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
LL ans=solve(b/k,d/k)-solve((a-1)/k,d/k)-solve(b/k,(c-1)/k)+solve((a-1)/k,(c-1)/k);
printf("%lld\n",ans);
}
return 0;
}