题目描述
对于给出的 n n n个询问,每次求有多少个数对 ( x , y ) (x,y) (x,y),满足 a ≤ x ≤ b a≤x≤b a≤x≤b, c ≤ y ≤ d c≤y≤d c≤y≤d,且 g c d ( x , y ) = k gcd(x,y) = k gcd(x,y)=k, g c d ( x , y ) gcd(x,y) gcd(x,y)函数为 x x x和 y y y的最大公约数。
题解
先求 1 − x 1-x 1−x, 1 − y 1-y 1−y的满足 g c d ( x , y ) = k gcd(x,y)\ =\ k gcd(x,y) = k的数对:我们设 f ( i ) f(i) f(i)表示 k ∣ g c d ( x , y ) k|gcd(x,y) k∣gcd(x,y)的方案书, g ( i ) g(i) g(i)表示 g c d ( x , y ) = k gcd(x,y)=k gcd(x,y)=k的方案数。
根据莫比乌斯反演公式,则一定有:
f ( i ) = ∑ d = 1 ⌊ n i ⌋ g ( i ∗ d )    ⟺    g ( i ) = ∑ d = 1 ⌊ n i ⌋ μ ( d ) ∗ f ( i ∗ d ) f(i)\ =\ \sum_{d=1}^{\lfloor \frac{n}{i}\rfloor}\ g(i*d)\iff g(i)\ =\ \sum_{d=1}^{\lfloor \frac{n}{i}\rfloor}μ(d)*f(i*d) f(i) = d=1∑⌊in⌋ g(i∗d)⟺g(i) = d=1∑⌊in⌋μ(d)∗f(i∗d)
因此对于前者μ(d),我们可以使用前缀和来进行求解。对于后者,我们考虑除法分块来实现。
根据f数组的定义,我们可以知道 f ( i ∗ d ) = ⌊ n i ∗ d ⌋ ⌊ m i ∗ d ⌋ f(i*d)\ =\lfloor \frac{n}{i*d}\rfloor \lfloor \frac{m}{i*d}\rfloor f(i∗d) =⌊i∗dn⌋⌊i∗dm⌋.
因此我们需要利用除法分块来求解 f ( i ∗ d ) f(i*d) f(i∗d).显然对于 ⌊ n i ∗ d ⌋ \lfloor \frac{n}{i*d}\rfloor ⌊i∗dn⌋和对于 ⌊ m i ∗ d ⌋ \lfloor \frac{m}{i*d}\rfloor ⌊i∗dm⌋会形成不同的序列,每次对于i求解两个右端点中较小点 = j =j =j即可。
设 a n s ( x , y ) ans(x,y) ans(x,y)表示 1 − x , 1 − y 1-x,1-y 1−x,1−y的答案。
现在考虑原题,由于有范围限制,根据容斥原理就很容易就想到:
a
n
s
=
a
n
s
(
b
,
d
)
−
a
n
s
(
a
−
1
,
d
)
−
a
n
s
(
b
,
c
−
1
)
+
a
n
s
(
a
−
1
,
c
−
1
)
ans\ =\ ans(b,d)-ans(a-1,d)-ans(b,c-1)+ans(a-1,c-1)
ans = ans(b,d)−ans(a−1,d)−ans(b,c−1)+ans(a−1,c−1)
然后代码就很好实现了:
#include <bits/stdc++.h>
using namespace std;
const int N = 100000;
int Miu[N+10];
int vis[N+10];
int sum[N+10];
int prime[N+10];
void Find_Miu(void)
{
int m = 0;
Miu[1] = 1;
for (int i=2;i<=N;++i)
{
if (vis[i] == 0) prime[++m] = i, Miu[i] = -1;
for (int j=1;j<=m && i*prime[j]<=N;++j)
{
vis[i*prime[j]] = 1;
if (i%prime[j] == 0)
{
Miu[i*prime[j]] = 0;
break;
}
Miu[i*prime[j]] = -Miu[i];
}
}
return;
}
void Get_sum(void)
{
for (int i=1;i<=N;++i)
sum[i] = sum[i-1]+Miu[i];
return;
}
long long get(int a,int b,int d)
{
int j,n,m;
n = a/d;
m = b/d;
long long ans = 0;
for (int i=1;i<=min(n,m);i=j+1)//不要写成i++
{
j = min(n/(n/i),m/(m/i));
ans += (long long)(sum[j]-sum[i-1])*(n/i)*(m/i);
}
return ans;
}
void work(void)
{
int a,b,c,d,k;
scanf("%d %d %d %d %d",&a,&b,&c,&d,&k);
printf("%lld\n",get(b,d,k)-get(a-1,d,k)-get(b,c-1,k)+get(a-1,c-1,k));
return;
}
int main(void)
{
int n;
Find_Miu();
Get_sum();
scanf("%d",&n);
while (n -- ) work();
return 0;
}