洛谷P3327 [SDOI2015]约数个数和
标签
- 莫比乌斯反演
- 整除分块
- 线性筛
前言
- 这里的整除分块是另一种常见的形式。我半天都没搞清楚是怎么分的…好久之后才恍然大悟
简明题意
-
d
(
x
)
d(x)
d(x)表示
x
x
x的约数个数。给定
n
,
m
n,m
n,m,求
∑ i = 1 n ∑ j = 1 m d ( i ∗ j ) \sum_{i=1}^n\sum_{j=1}^md(i*j) i=1∑nj=1∑md(i∗j)
思路
- 首先大家应该知道这样一个很常用的式子:
d ( i ∗ j ) = ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = = 1 ] d(i*j)=\sum_{x|i}\sum_{y|j}[gcd(x,y)==1] d(i∗j)=x∣i∑y∣j∑[gcd(x,y)==1]
这个式子记住就好
- 我们用这个式子计算
d
(
i
,
j
)
d(i,j)
d(i,j),得出原式等于:
∑ i = 1 n ∑ j = 1 m d ( i j ) = ∑ i = 1 n ∑ j = 1 m ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = = 1 ] \sum_{i=1}^n\sum_{j=1}^md(ij)=\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[gcd(x,y)==1] i=1∑nj=1∑md(ij)=i=1∑nj=1∑mx∣i∑y∣j∑[gcd(x,y)==1] -
x
,
y
x,y
x,y是
i
,
j
i,j
i,j的因数,暴力计算因数复杂度太高了,我们去枚举
x
,
y
x,y
x,y,显然
n
,
m
n,m
n,m的约数分别最大不超过
n
,
m
n,m
n,m,因此,原式变成:
∑ x = 1 n ∑ y = 1 m ( [ n x ] [ m y ] [ g c d ( x , y ) = = 1 ] ) \sum_{x=1}^n\sum_{y=1}^m\left([\frac nx][\frac my][gcd(x,y)==1]\right) x=1∑ny=1∑m([xn][ym][gcd(x,y)==1]) - 对于
[
g
c
d
(
x
,
y
)
=
=
1
]
[gcd(x,y)==1]
[gcd(x,y)==1]我们很快能想到用莫比乌斯函数性质替换成
∑
d
∣
g
c
d
(
i
,
j
)
μ
(
d
)
\sum\limits_{d|gcd(i,j)}\mu(d)
d∣gcd(i,j)∑μ(d),而又有这样的性质:
d
∣
g
c
d
(
i
,
j
)
  
⟺
  
d
∣
i
且
d
∣
j
d|gcd(i,j)\iff d|i 且d|j
d∣gcd(i,j)⟺d∣i且d∣j 于是就成了:
∑ i = 1 n ∑ j = 1 m ( [ n i ] [ m j ] ∑ d ∣ i 且 d ∣ j μ ( d ) ) \sum_{i=1}^n\sum_{j=1}^m\left([\frac ni][\frac mj]\sum\limits_{d|i且d|j}\mu(d)\right) i=1∑nj=1∑m⎝⎛[in][jm]d∣i且d∣j∑μ(d)⎠⎞ - 换了之后就可以改为枚举
d
d
d,
d
d
d作为
n
n
n的约数,显然
d
d
d的上限是
n
n
n。就成了:
∑ d = 1 n μ ( d ) ∑ i = 1 n ∑ j = 1 m ( [ d ∣ i 且 d ∣ j ] ∗ [ n i ] ∗ [ m j ] ) \sum_{d=1}^n\mu(d)\sum_{i=1}^n\sum_{j=1}^m\left([d|i且d|j]*[\frac ni]*[\frac mj] \right) d=1∑nμ(d)i=1∑nj=1∑m([d∣i且d∣j]∗[in]∗[jm]) - 又有
∑ i = 1 n ∑ j = 1 m ( [ d ∣ i 且 d ∣ j ] ∗ [ n i ] ∗ [ m j ] ) = ∑ i = 1 [ n d ] ∑ j = 1 [ m d ] [ n i d ] ∗ [ m j d ] \sum\limits_{i=1}^n\sum\limits_{j=1}^m\left([d|i且d|j]*[\frac ni]*[\frac mj]\right)=\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}] i=1∑nj=1∑m([d∣i且d∣j]∗[in]∗[jm])=i=1∑[dn]j=1∑[dm][idn]∗[jdm]
推导:我们可以发现, ∑ i = 1 n ∑ j = 1 m [ d ∣ i 且 d ∣ j ] \sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j] i=1∑nj=1∑m[d∣i且d∣j],实际有效的二元组 ( i , j ) (i,j) (i,j),跟 ∑ i = 1 [ n d ] ∑ j = 1 [ m d ] \sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]} i=1∑[dn]j=1∑[dm]所枚举的二元组 ( i , j ) (i,j) (i,j)在数量上是一样多的。在数值上,前者二元组的大小是后者的 d d d倍。因此,变换上限后, i , j i,j i,j成为了原来的 1 d \frac 1d d1,我们给它们乘上 d d d就回到原来的项了。
- 接下来,原式就会变成:
∑ d = 1 n μ ( d ) ∑ i = 1 [ n d ] ∑ j = 1 [ m d ] [ n i d ] ∗ [ m j d ] \sum_{d=1}^n\mu(d)\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}] d=1∑nμ(d)i=1∑[dn]j=1∑[dm][idn]∗[jdm] - 这个时候,对于后面的式子
∑
i
=
1
[
n
d
]
∑
j
=
1
[
m
d
]
[
n
i
d
]
∗
[
m
j
d
]
\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}]
i=1∑[dn]j=1∑[dm][idn]∗[jdm]显然是可以通过移项改成两项相乘:
∑
i
=
1
[
n
d
]
[
n
i
d
]
∑
j
=
1
[
m
d
]
[
m
j
d
]
\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]
i=1∑[dn][idn]j=1∑[dm][jdm],然后原式就成了:
∑ d = 1 n μ ( d ) ∑ i = 1 [ n d ] [ n i d ] ∑ j = 1 [ m d ] [ m j d ] \sum_{d=1}^n\mu(d)\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}] d=1∑nμ(d)i=1∑[dn][idn]j=1∑[dm][jdm] - 这里观察 ∑ i = 1 [ n d ] [ n i d ] ∑ j = 1 [ m d ] [ m j d ] \sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}] i=1∑[dn][idn]j=1∑[dm][jdm]是可以预处理的。这里有一个小难点,就是这里未知的既有 d d d,又有 n , m n,m n,m,预处理需要枚举 d , n , m d,n,m d,n,m,那复杂度岂不是 O ( n 3 ) O(n^3) O(n3)了?大家应该摒弃这种观念,思维不能定势。首先我们并不需要直接对 ∑ i = 1 [ n d ] [ n i d ] ∑ j = 1 [ m d ] [ m j d ] \sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}] i=1∑[dn][idn]j=1∑[dm][jdm]这整个式子预处理,可以分开对 ∑ i = 1 [ n d ] [ n i d ] \sum\limits_{i=1}^{[\frac nd]}[\frac n{id}] i=1∑[dn][idn]和 ∑ j = 1 [ m d ] [ m j d ] \sum\limits_{j=1}^{[\frac md]}[\frac m{jd}] j=1∑[dm][jdm]预处理,所以现在枚举的又可以减少为 d , m d,m d,m两种了,复杂度 O ( n 2 ) O(n^2) O(n2)。但是现在注意观察, n d nd nd在整个式子里都是一个整体,他们整体取值的范围是确定的。因此可以直接枚举 n d \frac nd dn,这样预处理复杂度就是 O ( n n ) O(n\sqrt n) O(nn),总复杂度就是: O ( n n + n ∗ T ) O(n\sqrt n+n*T) O(nn+n∗T)。
- 但是,这里有复杂度更低的方法,我们令
f
(
x
)
=
∑
i
=
1
x
[
x
i
]
f(x)=\sum\limits_{i=1}^x[\frac xi]
f(x)=i=1∑x[ix],然后原式就变成:
∑ d = 1 n ( μ ( d ) ∗ f ( [ n d ] ) ∗ f ( [ m d ] ) ) \sum_{d=1}^n\left(\mu(d)*f([\frac nd])*f([\frac md])\right) d=1∑n(μ(d)∗f([dn])∗f([dm])) - f f f的任意项是可以通过前面所说的预处理出来,从而 O ( 1 ) O(1) O(1)查询。但是,这里注意到 [ n d ] [\frac nd] [dn]和 [ m d ] [\frac md] [dm]是可以分块的,也就是对于一块 [ l , r ] [l,r] [l,r],这个区间的 [ n d ] [\frac nd] [dn]和 [ m d ] [\frac md] [dm]是确定的,也就是说这个区间的 f ( [ n d ] ) ∗ f ( [ m d ] ) f([\frac nd])*f([\frac md]) f([dn])∗f([dm])是确定的。所以只需要用这个区间的 f ( [ n d ] ) ∗ f ( [ m d ] ) f([\frac nd])*f([\frac md]) f([dn])∗f([dm])乘以 μ ( d ) \mu(d) μ(d)区间和就可以了,最终复杂度降低为 O ( n n + n ∗ T ) O(n\sqrt n+\sqrt{n}*T) O(nn+n∗T),可以通过这一题
- 其实还可以再做一个小优化。之前我们定义了 f ( x ) = ∑ i = 1 x [ x i ] f(x)=\sum\limits_{i=1}^x[\frac xi] f(x)=i=1∑x[ix],如果数论比较好的同学可以立马发现, f ( x ) f(x) f(x)就是 [ 1 , x ] [1,x] [1,x]的约数个数之和,所以我们实际上可以线筛预处理出 d d d函数,然后做一遍前缀和。这样优化大概会快4倍
注意事项
总结
- 对于式子
∑
i
=
1
n
∑
j
=
1
m
[
d
∣
i
且
d
∣
j
]
\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]
i=1∑nj=1∑m[d∣i且d∣j]很显然它等于
∑
i
=
1
[
n
d
]
∑
j
=
1
[
m
d
]
1
=
[
n
d
]
∗
[
m
d
]
\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}1=[\frac nd]*[\frac md]
i=1∑[dn]j=1∑[dm]1=[dn]∗[dm],而对于
∑
i
=
1
n
∑
j
=
1
m
[
d
∣
i
且
d
∣
j
]
∗
[
n
i
]
∗
[
m
j
]
\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]*[\frac ni]*[\frac mj]
i=1∑nj=1∑m[d∣i且d∣j]∗[in]∗[jm],实际上,将枚举上限分别换成
[
n
d
]
[\frac nd]
[dn]和
[
m
d
]
[\frac md]
[dm],我们枚举的就是所有
[
d
∣
i
且
d
∣
j
]
[d|i且d|j]
[d∣i且d∣j]的二元组
[
i
,
j
]
[i,j]
[i,j]的
1
d
\frac 1d
d1倍。然后我们计算
[
n
i
]
∗
[
m
j
]
[\frac ni]*[\frac mj]
[in]∗[jm]的时候要将
i
j
ij
ij放大
d
d
d倍,因此实际上我们计算的就应该是
[
n
i
d
]
∗
[
m
j
d
]
[\frac n{id}]*[\frac m{jd}]
[idn]∗[jdm],所以就有:
∑ i = 1 n ∑ j = 1 m [ d ∣ i 且 d ∣ j ] ∗ [ n i ] ∗ [ m j ] = ∑ i = 1 [ n d ] ∑ j = 1 [ m d ] [ n i d ] ∗ [ m j d ] \sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]*[\frac ni]*[\frac mj]=\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}] i=1∑nj=1∑m[d∣i且d∣j]∗[in]∗[jm]=i=1∑[dn]j=1∑[dm][idn]∗[jdm] - 对于这样的式子
∑ i = 1 n ∑ j = 1 m i j = ( ∑ i = 1 n i ) ∗ ( ∑ j = 1 m j ) \sum_{i=1}^n\sum_{j=1}^mij=\left(\sum_{i=1}^ni\right)*\left(\sum_{j=1}^mj\right) i=1∑nj=1∑mij=(i=1∑ni)∗(j=1∑mj)这样可以直接将 O ( n 2 ) O(n^2) O(n2)的复杂度降低为 O ( n ) O(n) O(n) - 整除分块,最经典的是处理 ∑ i = 1 n [ x i ] \sum\limits_{i=1}^n[\frac xi] i=1∑n[ix]。但有另一种形式也是常用的: ∑ i = 1 n f ( [ x i ] ) \sum\limits_{i=1}^nf([\frac xi]) i=1∑nf([ix]),同样每一块的 [ n i ] [\frac ni] [in]是相同的,就可以对相同 k k k个,算一遍 k ∗ f [ x i ] k*f[\frac xi] k∗f[ix]
- 对 d d d函数的线性筛,需要多开一个数组 n u m [ i ] num[i] num[i]记录 i i i的最小质因子出现的次数。处理时,如果 i i%prime[j]==0 i,也就是含有最小值因子 p r i m e [ j ] prime[j] prime[j], n u m [ i ] num[i] num[i]就应该更新成 n u m [ i ] + 1 num[i]+1 num[i]+1,否则的话,说明 i i i里面没有质因子 p r i m e [ j ] prime[j] prime[j],而 p r i m e [ j ] prime[j] prime[j]是新的最小质因子,因此num[i*prime[j]]=1(这里容易出错,要谨记!!!)
AC代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 50000 + 10;
bool no_prime[maxn];
int prime[maxn], mu[maxn], pre_mu[maxn], dd[maxn], num[maxn];
long long pre_dd[maxn];
int shai(int n)
{
int cnt = 0;
mu[1] = dd[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!no_prime[i])
prime[++cnt] = i, mu[i] = -1, dd[i] = 2, num[i] = 1;
for (int j = 1; j <= cnt && prime[j] * i <= n; j++)
{
no_prime[prime[j] * i] = 1;
mu[prime[j] * i] = (i % prime[j] == 0) ? 0 : -mu[i];
dd[prime[j] * i] = (i % prime[j] == 0) ? dd[i] / (num[i] + 1) * (num[i] + 2) : dd[i] * 2;
num[prime[j] * i] = (i % prime[j] == 0) ? num[i] + 1 : 1;
if (i % prime[j] == 0) break;
}
}
for (int i = 1; i <= n; i++)
pre_mu[i] = pre_mu[i - 1] + mu[i], pre_dd[i] = pre_dd[i - 1] + dd[i];
return cnt;
}
long long cal2(int n, int m)
{
int l = 1, r;
long long ans = 0;
while (l <= n)
{
r = min(n / (n / l), m / (m / l));
ans += 1ll * (pre_mu[r] - pre_mu[l - 1]) * pre_dd[n / l] * pre_dd[m / l];
l = r + 1;
}
return ans;
}
void solve()
{
shai(maxn - 10);
int t;
scanf("%d", &t);
while (t--)
{
int n, m;
scanf("%d%d", &n, &m);
if (n > m) swap(n, m);
printf("%lld\n", cal2(n, m));
}
}
int main()
{
solve();
return 0;
}