题意
对于给出的T个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k。
1≤T,a,b,c,d,k≤50000
题解
莫比乌斯反演经典入门题。
首先用容斥,把问题转化为1<=x<=n且1<=y<=m的。
设
f(i)
表示满足gcd(x,y)等于i的有序数对(x,y)的个数。(1<=x<=n且1<=y<=m)
构造
F(i)=∑i|df(d)
,即满足i|gcd(x,y)的有序数对(x,y)的个数。
F(i)
很好求,只有x和y都是i的倍数即可,所以
F(i)=⌊ni⌋⌊mi⌋
。
下面进行反演:
F(i)=∑i|df(d)⇒f(i)=∑i|dμ(di)F(d)=∑i|dμ(di)⌊nd⌋⌊md⌋
还可以转化一下:使gcd(x,y)=K的方案数(1<=x<=n且1<=y<=m) 与 使gcd(x,y)=1的方案数(1<=x<=n/k且1<=y<=m/k) 是等价的。
然后我们的目标就是求得 f(1)=∑min(n/K,m/K)dμ(d)⌊n/Kd⌋⌊m/Kd⌋
这个如果O(n)求的话还是太慢,要想更优的办法。
注意到 ⌊n/Kd⌋ 这种东西的取值是一段一段的,然后后我们对 μ 求前缀和,然后分块求和,把一段连续的 ⌊n/Kd⌋⌊m/Kd⌋ 值相同的一起算。
具体实现:
if(n>m) swap(n,m);
int res=0;
for(int d=1,nxt=0;d<=n;d=nxt+1){
nxt=min(n/(n/d),m/(m/d));
res+=(n/d)*(m/d)*(sum_mu[nxt]-sum_mu[d-1]);
}
这样就变成 O(n−−√) 的了。
总复杂度 O(T∗n−−√)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000005, N=1000000;
int _test,K,p[maxn],mu[maxn],sum_mu[maxn];
bool vis[maxn];
void get_mu(){
memset(vis,1,sizeof(vis));
mu[1]=1;
for(int i=2;i<=N;i++){
if(vis[i]) p[++p[0]]=i, mu[i]=-1;
for(int j=1;j<=p[0]&&i*p[j]<=N;j++){
vis[i*p[j]]=false;
if(i%p[j]==0){ mu[i*p[j]]=0; break; }
mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<=N;i++) sum_mu[i]=sum_mu[i-1]+mu[i];
}
int get(int n,int m){
n/=K; m/=K;
if(n>m) swap(n,m);
int res=0;
for(int d=1,nxt=0;d<=n;d=nxt+1){
nxt=min(n/(n/d),m/(m/d));
res+=(n/d)*(m/d)*(sum_mu[nxt]-sum_mu[d-1]);
}
return res;
}
int main(){
freopen("bzoj2301.in","r",stdin);
freopen("bzoj2301.out","w",stdout);
get_mu();
scanf("%d",&_test);
while(_test--){
int a,b,c,d;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&K);
printf("%d\n",get(b,d)-get(a-1,d)-get(b,c-1)+get(a-1,c-1));
}
return 0;
}