原题链接:洛谷P3455 [POI2007]ZAP-Queries
题目描述
FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对 x , y x,y x,y,满足 x ≤ a , y ≤ b x\leq a,y\leq b x≤a,y≤b,并且 g c d ( x , y ) = d gcd(x,y)=d gcd(x,y)=d。FGD希望得到你的帮助。
输入格式
第一行一个整数
n
(
1
≤
n
≤
50000
n (1 \leq n \leq 50000
n(1≤n≤50000),代表询问个数。
接下来
n
n
n行每行三个整数: a, b, d
(
1
≤
d
≤
a
,
b
≤
50000
)
(1\leq d\leq a,b\leq 50000)
(1≤d≤a,b≤50000), 用空格分开。
输出格式
共 n n n行,每行一个整数,代表每次询问的答案。
输入输出样例
- 输入1
2
4 5 2
6 4 3
- 输出1
3
2
题解
此题为莫比乌斯反演入门题,让我们对莫比乌斯反演题是要我们做什么有个概念。有关莫比乌斯反演,可参考莫比乌斯反演及的证明与应用
我们设:
f
(
x
)
=
∑
i
=
1
a
∑
j
=
1
b
[
g
c
d
(
i
,
j
)
=
x
]
,
f(x)=\sum_{i=1}^{a}\sum_{j=1}^{b}[gcd(i,j)=x],
f(x)=i=1∑aj=1∑b[gcd(i,j)=x],
F
(
x
)
=
∑
x
∣
k
f
(
k
)
=
∑
i
=
1
a
∑
j
=
1
b
[
x
∣
g
c
d
(
i
,
j
)
]
F(x)=\sum_{x|k}f(k)=\sum_{i=1}^{a}\sum_{j=1}^{b}[x|gcd(i,j)]
F(x)=x∣k∑f(k)=i=1∑aj=1∑b[x∣gcd(i,j)]
=
∑
i
=
1
a
[
x
∣
i
]
∑
j
=
1
b
[
x
∣
j
]
=
⌊
a
x
⌋
⌊
b
x
⌋
=\sum_{i=1}^{a}[x|i]\sum_{j=1}^{b}[x|j]=\lfloor\frac{a}{x}\rfloor\lfloor\frac{b}{x}\rfloor
=i=1∑a[x∣i]j=1∑b[x∣j]=⌊xa⌋⌊xb⌋
则可以由莫比乌斯反演可以推出:
f
(
x
)
=
∑
x
∣
k
μ
(
⌊
k
x
⌋
)
F
(
k
)
f(x)=\sum_{x|k}\mu(\lfloor\frac{k}{x}\rfloor)F(k)
f(x)=x∣k∑μ(⌊xk⌋)F(k)
设完这两个函数之后,我们便发现,
A
n
s
=
f
(
d
)
Ans=f(d)
Ans=f(d)。那么,
A
n
s
=
∑
d
∣
k
μ
(
⌊
k
d
⌋
)
F
(
k
)
Ans=\sum_{d|k}\mu(\lfloor\frac{k}{d}\rfloor)F(k)
Ans=d∣k∑μ(⌊dk⌋)F(k)
设
t
=
k
d
t=\frac{k}{d}
t=dk。我们枚举
t
t
t:
A
n
s
=
∑
t
=
1
μ
(
t
)
⌊
a
t
d
⌋
⌊
b
t
d
⌋
Ans=\sum_{t=1}\mu(t)\lfloor\frac{a}{td}\rfloor\lfloor\frac{b}{td}\rfloor
Ans=t=1∑μ(t)⌊tda⌋⌊tdb⌋
由于有多组数据,所以我们再用一下整除分块预处理,这样单次询问就可以
O
(
n
)
O(\sqrt{n})
O(n)了。要注意的是,当
t
d
>
m
i
n
(
a
,
b
)
td>min(a,b)
td>min(a,b)时就可以结束累加。
当然还有最后一个问题:
⌊
a
t
d
⌋
\lfloor\frac{a}{td}\rfloor
⌊tda⌋含有一个常量
d
d
d,如何整除分块?
设
a
=
k
t
+
b
a=kt+b
a=kt+b
(
0
≤
b
<
t
)
(0\leq b<t)
(0≤b<t),
k
=
p
d
+
q
k=pd+q
k=pd+q
(
0
≤
q
<
d
)
(0\leq q<d)
(0≤q<d), 那
⌊
⌊
a
t
⌋
d
⌋
=
⌊
k
d
⌋
=
p
\lfloor\frac{\lfloor\frac{a}{t}\rfloor}{d}\rfloor=\lfloor\frac{k}{d}\rfloor=p
⌊d⌊ta⌋⌋=⌊dk⌋=p
⌊
a
t
d
⌋
=
⌊
(
p
d
+
q
)
t
+
b
t
d
⌋
\lfloor\frac{a}{td}\rfloor=\lfloor\frac{(pd+q)t+b}{td}\rfloor
⌊tda⌋=⌊td(pd+q)t+b⌋
=
⌊
p
t
d
+
(
t
q
+
b
)
t
d
⌋
=\lfloor\frac{ptd+(tq+b)}{td}\rfloor
=⌊tdptd+(tq+b)⌋
p
+
⌊
t
q
+
b
t
d
⌋
=
p
p+\lfloor\frac{tq+b}{td}\rfloor=p
p+⌊tdtq+b⌋=p
因此
⌊
a
t
d
⌋
=
⌊
⌊
a
t
⌋
d
⌋
=
⌊
⌊
a
d
⌋
t
⌋
,
\lfloor\frac{a}{td}\rfloor=\lfloor\frac{\lfloor\frac{a}{t}\rfloor}{d}\rfloor=\lfloor\frac{\lfloor\frac{a}{d}\rfloor}{t}\rfloor,
⌊tda⌋=⌊d⌊ta⌋⌋=⌊t⌊da⌋⌋,
整除分块时块与块之间的距离不小于
⌊
a
t
⌋
\lfloor\frac{a}{t}\rfloor
⌊ta⌋。当然,我们也可以先将
a
,
b
a,b
a,b分别除以
d
d
d之后再进行分块。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=50005;
int n,a,b,d;
int num[maxn],prime[maxn],p_num;
int mu[maxn];
int sum[maxn];
void get_mu()
{
mu[1]=1;
for(int i=2;i<maxn;i++)
{
if(!num[i])
{
prime[++p_num]=i;
mu[i]=-1;
}
for(int j=1;j<=p_num&&i*prime[j]<maxn;j++)
{
num[i*prime[j]]=1;
if(i%prime[j]) mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]]=0;
break;
}
}
}
//预处理mu的前缀和。
for(int i=1;i<maxn;i++) sum[i]=sum[i-1]+mu[i];
}
int main()
{
scanf("%d",&n);
get_mu();
while(n--)
{
int ans=0;
scanf("%d%d%d",&a,&b,&d);
int up=min(a,b);
//数论分块
//另一种方法:
//int ss=a/d,sss=b/d;
for(int i=1,j;i*d<=up;i=j+1)
{
j=min(a/(a/i),b/(b/i));
//另一种方法:
//j=min(ss/(ss/i),sss/(sss/i));
ans+=(a/(i*d))*(b/(i*d))*(sum[j]-sum[i-1]);
}
printf("%d\n",ans);
}
return 0;
}