Description

有n个询问(n≤50000),每个询问有五个整数a,b,c,d,k,求有多少个数对(x,y)满足a≤x≤b,c≤y≤d,且gcd(x,y)=k.(a≤b≤50000,c≤d≤50000,k≤50000)

Solution

我们发现,计算一个数x在某个闭区间[a,b]内的因数数量并不是很方便,可以转化为x在区间[1,b]的因数的数量-x在区间[1,a-1]的因数的数量(因为求[1,Z]比较好求)。

原问题是,对于所有x∈[a,b],y∈[c,d],求所有gcd(x,y)=k的点对的数量。
根据容斥原理,问题转化为,计算:
所有x∈[1,b],y∈[1,d],所有gcd(x,y)=k的点对的数量
-所有x∈[1,a-1],y∈[1,d],所有gcd(x,y)=k的点对的数量
-所有x∈[1,b],y∈[1,c-1],所有gcd(x,y)=k的点对的数量
+所有x∈[1,a-1],y∈[1,c-1],所有gcd(x,y)=k的点对的数量

所以怎么求解任意x∈[1,n],y∈[1,m],所有gcd(x,y)=k的点对的数量呢?

设f(k)=任意x∈[1,n],y∈[1,m],所有gcd(x,y)=k的点对的数量,我们发现并不好求,于是再设F(k)=任意x∈[1,n],y∈[1,m],所有满足k整除gcd(x,y)的点对的数量
因为F值比f值容易求,所以我们这时可以运用莫比乌斯反演了。

容易发现,只要是k的倍数的f值都对F(k)有贡献,即F(k)=kdf(d)F(k) = \sum_{k|d}f(d)

我们可以知道F(k)=nkmkF(k)=\lfloor \frac{n}{k} \rfloor \lfloor \frac{m}{k} \rfloor,即n内有n/k个数是k的倍数,m内有n/k个数是k的倍数,他们两两之间的最小公因数一定包含k。

于是$$f(k)=\sum_{k|d}\mu(\frac{d}{k})\lfloor \frac{n}{d} \rfloor\lfloor \frac{m}{d} \rfloor$$
这样我们就可以在线性时间求出f的值了。
然而我们发现,每个询问的需要计算四个f,复杂度依然达到平方级,仍需优化。

还有哪里可以优化呢?
我们发现,其实有一些k,他们的nkmk\lfloor \frac{n}{k} \rfloor \lfloor \frac{m}{k} \rfloor是一样的,我们还要对他们分别求和,拉慢了运行速度。

因为不同nkmk\lfloor \frac{n}{k} \rfloor \lfloor \frac{m}{k} \rfloor值最多2(n+m)2 * (\sqrt{n}+\sqrt{m})个(证明如下)

可以发现,nk\lfloor \frac{n}{k} \rfloor的值最多2n2 \sqrt{n}个(小于n\sqrt{n}的因数xx对应一个大于n\sqrt{n}的因数n/x),我们将nk\lfloor \frac{n}{k} \rfloor值相等的k在数轴上分到一个区间,(如下图),则最多2n2 \sqrt{n}个区间,相同的,mk\lfloor \frac{m}{k} \rfloor的值最多2n2 \sqrt{n}值相等的k最多2m2 \sqrt{m}个区间。以n=6,m=8为例。

就是说,nk\lfloor \frac{n}{k} \rfloor2n2 \sqrt{n}取值,nk\lfloor \frac{n}{k} \rfloor3n3 \sqrt{n}取值,它们对应起来的取值,就是划分的2(nk+mk)2(\sqrt{\frac{n}{k}}+\sqrt{\frac{m}{k}})区间的区间。

nkmk\lfloor \frac{n}{k} \rfloor \lfloor \frac{m}{k} \rfloor值相同的k显然是连续的,我们可以对μ求一下前缀和,对于值相同的k直接乘上他们的μ值和即可,这样就可以将复杂度优化到Θ(nmax(b,d))\Theta(n * \sqrt{max(b,d)})了。

然而我们怎么找到每个区间的起点和终点呢?我们发现,m/k可以找到floor值为k的区间的末尾值,例如第一个数轴中6/floor(6/5)可以找到5所在区间的末尾6,第二个数轴中8/floor(8/5)可以找到5所在区间的末尾8,这样,区间起点为i,这个区间末尾就是min(floor(n/(n/i)),floor(m/(m/i))).

其实还可以更优化,求所有x∈[a,b],y∈[c,d],求所有gcd(x,y)=k的点对的数量可转化为x∈[a/k,b/k],y∈[c/k,d/k],求所有gcd(x,y)=1的点对的数量,只要在程序里加一句话就可以提速50%了,留给读者自己思考。

之后的事就非常愉快辣!

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn=50000+10;

bool form[maxn];
int prime[maxn],mu[maxn]={0,1},sum[maxn],cnt;
void MakeMuList(){
for(int i=2;i<maxn;i++){
if(!form[i]) prime[cnt++]=i,mu[i]=-1;
for(int j=0;prime[j]*i<maxn;j++){
form[prime[j]*i]=true;
if(!(i%prime[j])){
mu[prime[j]*i]=0;
break;
}
mu[prime[j]*i]=-mu[i];
}
}
for(int i=1;i<maxn;i++) sum[i]=sum[i-1]+mu[i];
}

int f(int n,int m,int k){
int ans=0;
if(n>m) swap(n,m);
for(int i=1,last;i<=n;i=last+1){
last=min(n/(n/i),m/(m/i));
ans+=(sum[last]-sum[i-1])*(m/(i*k))*(n/(i*k));
}
return ans;
}

int main(){
MakeMuList();
int kase; scanf("%d",&kase);
while(kase--){
int a,b,c,d,k; scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("%d\n",f(b,d,k)-f(a-1,d,k)-f(b,c-1,k)+f(a-1,c-1,k));
}
return 0;
}

Comment