[题解] 约数个数和 数论分块+莫比乌斯反演
题目链接
首先我们需要知道一个结论:
d
(
i
j
)
=
∑
x
∣
i
∑
y
∣
j
[
gcd
(
x
,
y
)
=
=
1
]
d(ij) = \sum_{x|i}\sum_{y|j}\left[\gcd(x,y)==1\right]
d(ij)=x∣i∑y∣j∑[gcd(x,y)==1]
感性理解就是对约数取了个并集。
对于这个结论,我们还有其扩展形式。
d ( i j k ) = ∑ x ∣ i ∑ y ∣ j ∑ z ∣ k [ gcd ( x , y ) = 1 ] [ gcd ( y , z ) = 1 ] [ gcd ( x , z ) = 1 ] d(ijk) = \sum_{x|i}\sum_{y|j}\sum_{z|k}[\gcd(x,y) = 1][\gcd(y,z) = 1][\gcd(x,z) = 1] d(ijk)=x∣i∑y∣j∑z∣k∑[gcd(x,y)=1][gcd(y,z)=1][gcd(x,z)=1]
我们对这个式子进行变形:
d
(
i
j
)
=
∑
x
∣
i
∑
y
∣
j
[
gcd
(
x
,
y
)
=
=
1
]
=
∑
x
∣
i
∑
y
∣
j
∑
k
∣
gcd
(
x
,
y
)
μ
(
k
)
=
∑
k
∣
i
,
k
∣
j
μ
(
k
)
∑
x
∣
i
∑
y
∣
j
1
\begin{aligned} d(ij) =&\sum_{x|i}\sum_{y|j}\left[\gcd(x,y)==1\right]\\ =&\sum_{x|i}\sum_{y|j}\sum_{k|\gcd(x,y)}\mu(k)\\ =&\sum_{k|i,k|j}\mu(k)\sum_{x|i}\sum_{y|j}1\\ \end{aligned}
d(ij)===x∣i∑y∣j∑[gcd(x,y)==1]x∣i∑y∣j∑k∣gcd(x,y)∑μ(k)k∣i,k∣j∑μ(k)x∣i∑y∣j∑1
有的同学可能会有疑问,为什么这里不让
k
k
k从
1
1
1到
min
(
i
,
j
)
\min(i,j)
min(i,j)开始枚举呢?因为这里的k没有遍历
1
→
min
(
i
,
j
)
1 \to\min(i,j)
1→min(i,j)。因为总是存在
k
<
min
(
i
,
j
)
k<\min(i,j)
k<min(i,j),使得
k
∤
i
k\nmid i
k∤i或
k
∤
j
k\nmid j
k∤j。
我们继续化简:
d
(
i
j
)
=
∑
k
∣
i
,
k
∣
j
μ
(
k
)
(
∑
x
∣
i
k
1
)
(
∑
y
∣
j
k
1
)
=
∑
k
∣
i
,
k
∣
j
μ
(
k
)
d
(
i
k
)
d
(
j
k
)
\begin{aligned} d(ij) =&\sum_{k|i,k|j}\mu(k)\left(\sum_{x|\frac{i}{k}}1\right)\left(\sum_{y|\frac{j}{k}}1\right)\\ =&\sum_{k|i,k|j}\mu(k)d\left(\dfrac{i}{k}\right)d\left(\dfrac{j}{k}\right) \\ \end{aligned}
d(ij)==k∣i,k∣j∑μ(k)⎝⎛x∣ki∑1⎠⎞⎝⎛y∣kj∑1⎠⎞k∣i,k∣j∑μ(k)d(ki)d(kj)
我们把这个式子代入原式:
∑
i
=
1
n
∑
j
=
1
m
d
(
i
j
)
=
∑
i
=
1
n
∑
j
=
1
m
∑
k
∣
i
,
k
∣
j
μ
(
k
)
d
(
i
k
)
d
(
j
k
)
=
∑
k
=
1
min
(
n
,
m
)
μ
(
k
)
∑
k
∣
i
i
≤
n
d
(
i
k
)
∑
k
∣
j
j
≤
m
d
(
j
k
)
=
∑
k
=
1
min
(
n
,
m
)
μ
(
k
)
(
∑
i
=
1
n
k
d
(
i
)
)
(
∑
j
=
1
m
k
d
(
j
)
)
\begin{aligned} \sum_{i=1}^{n}\sum_{j=1}^{m}d(ij) =&\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{k|i,k|j}\mu(k)d\left(\dfrac{i}{k}\right)d\left(\dfrac{j}{k}\right) \\ =&\sum_{k=1}^{\min(n,m)}\mu(k)\sum_{k|i}^{i\le n}d\left(\dfrac{i}{k}\right) \sum_{k|j}^{j\le m} d\left(\dfrac{j}{k}\right)\\ =&\sum_{k=1}^{\min(n,m)}\mu(k)\left(\sum_{i=1}^{\frac{n}{k}}d(i)\right)\left(\sum_{j=1}^{\frac{m}{k}}d(j)\right)\\ \end{aligned}
i=1∑nj=1∑md(ij)===i=1∑nj=1∑mk∣i,k∣j∑μ(k)d(ki)d(kj)k=1∑min(n,m)μ(k)k∣i∑i≤nd(ki)k∣j∑j≤md(kj)k=1∑min(n,m)μ(k)⎝⎛i=1∑knd(i)⎠⎞⎝⎛j=1∑kmd(j)⎠⎞
我们设
s
(
n
)
=
∑
i
=
1
n
d
(
i
)
s(n) = \sum_{i=1}^{n}d(i)
s(n)=i=1∑nd(i)
即
s
s
s是
d
d
d的前缀和,那么原式可以化为:
∑
k
=
1
min
(
n
,
m
)
μ
(
k
)
s
(
⌊
n
k
⌋
)
s
(
⌊
m
k
⌋
)
\sum_{k=1}^{\min(n,m)}\mu(k)s\left(\left \lfloor\dfrac{n}{k} \right \rfloor \right)s\left(\left \lfloor \dfrac{m}{k} \right \rfloor \right)
k=1∑min(n,m)μ(k)s(⌊kn⌋)s(⌊km⌋)
通过交换求和符号,我们减少了某些不必要的重复枚举,降低了时间复杂度。
*注意 对于函数
s
s
s,我们仍然可以对其用数论分块。运用时只需要把它看成一种映射即可。
前面的
μ
\mu
μ可以预处理出前缀和,后面应用二维数论分块,总的时间复杂度
O
(
n
+
T
n
)
O\left(n+T\sqrt{n}\right)
O(n+Tn)。
这里用线性筛维护
d
d
d的时候,我们需要记录每个正整数
i
i
i的最小素因子的次数
a
[
i
]
a[i]
a[i],便于更新
d
d
d.
维护一个关于数的最小素因子的数组是常用手法。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 5e4 + 10;
int T;
int p[maxn],cnt;
bool isp[maxn];
ll mu[maxn],d[maxn];
ll a[maxn];
void getp(){
isp[1] = 1;
mu[1] = d[1] = 1;
a[1] = 0;
for(int i = 2; i < maxn; i++){
if(!isp[i]){
p[++cnt] = i;
mu[i] = -1;
d[i] = 2;
a[i] = 1;
}
for(int j = 1; j <= cnt && i * p[j] < maxn; j++){
isp[i*p[j]] = 1;
if(i %p[j] == 0){
mu[i*p[j]] = 0;
a[i*p[j]] = a[i]+1;
d[i*p[j]] = d[i]/(a[i]+1)*(a[i]+2);
break;
}
else{
mu[i*p[j]] = -mu[i];
a[i*p[j]] = 1;
d[i*p[j]] = 2*d[i];
}
}
}
for(int i = 1; i < maxn; i++){
mu[i] = mu[i-1] + mu[i];
d[i] = d[i-1] + d[i];
}
}
void solve(){
getp();
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
int t = min(n,m);
int l = 1, r = t + 1;
ll ans = 0;
while(l <= t){
r = min(n/(n/l),m/(m/l));
ans += d[n/l]*d[m/l]*(mu[r] - mu[l-1]);
l = r+1;
}
printf("%lld\n",ans);
}
}
int main()
{
solve();
return 0;
}