文章目录
- 算法
- 概述
- 引理
- 数论分块结论(区间右端点公式)
- 过程
- N N N 维数论分块
- 向上取整的数论分块
- 例题
- H ( n ) H(n) H(n)
- [CQOI2007] 余数求和
- [清华集训2012] 模积和
算法
概述
数论分块可以快速计算一些含有除法向下取整的和式(即形如 ∑ i = 1 n f ( i ) g ( ⌊ n i ⌋ ) \sum_{i = 1}^{n}f(i)g(\left \lfloor \frac{n}{i}\right \rfloor) ∑i=1nf(i)g(⌊in⌋))。当可以在 O ( 1 ) O(1) O(1) 或者 O ( l o g 2 n ) O(log_2n) O(log2n) 的复杂度内计算 f ( r ) − f ( l ) f(r) - f(l) f(r)−f(l)时,数论分块就能在 O ( n ) O(\sqrt{n}) O(n) 或 O ( n l o g 2 n ) O(\sqrt{n}log_2n) O(nlog2n) 的复杂度内计算出上述和式的值。
它主要利用了富比尼定理(其实没啥用,不需要懂)。核心想法是 把 ⌊ n i ⌋ \left \lfloor \frac{n}{i} \right \rfloor ⌊in⌋ 相同的数打包同时计算。
引理
引理 1 1 1
∀ a , b , c ∈ Z , ⌊ a b c ⌋ = ⌊ ⌊ a b ⌋ c ⌋ \forall a,b,c \in Z,\left \lfloor \frac{a}{bc} \right \rfloor = \left \lfloor \frac{\left \lfloor \frac{a}{b} \right \rfloor}{c} \right \rfloor ∀a,b,c∈Z,⌊bca⌋=⌊c⌊ba⌋⌋
证明:
设
a
b
=
⌊
a
b
⌋
+
r
(
0
<
r
<
1
)
\frac{a}{b} = \left \lfloor \frac{a}{b} \right \rfloor + r(0 < r < 1)
ba=⌊ba⌋+r(0<r<1)
则
⌊
a
b
c
⌋
=
⌊
a
b
c
⌋
=
⌊
⌊
a
b
⌋
c
+
r
c
⌋
\left \lfloor \frac{a}{bc} \right \rfloor = \left \lfloor \frac{\frac{a}{b}}{c} \right \rfloor = \left \lfloor \frac{\left \lfloor \frac{a}{b} \right \rfloor}{c} + \frac{r}{c} \right \rfloor
⌊bca⌋=⌊cba⌋=⌊c⌊ba⌋+cr⌋
设
⌊
a
b
⌋
=
p
c
+
q
(
0
≤
q
<
c
)
\left \lfloor \frac{a}{b} \right \rfloor = pc + q(0 \leq q<c)
⌊ba⌋=pc+q(0≤q<c)
则
q
+
r
<
c
q + r < c
q+r<c
⌊
⌊
a
b
⌋
c
+
r
c
⌋
=
⌊
p
+
q
+
r
c
⌋
=
p
=
⌊
⌊
a
b
⌋
c
⌋
\left \lfloor \frac{\left \lfloor \frac{a}{b} \right \rfloor}{c} + \frac{r}{c} \right \rfloor = \left \lfloor p + \frac{q + r}{c} \right \rfloor = p = \left \lfloor \frac{\left \lfloor \frac{a}{b} \right \rfloor}{c} \right \rfloor
⌊c⌊ba⌋+cr⌋=⌊p+cq+r⌋=p=⌊c⌊ba⌋⌋
引理 2 2 2
∀ n ∈ N ∗ , ∣ { ⌊ n d ⌋ ∣ d ∈ N ∗ , d ≤ n } ∣ ≤ ⌊ 2 n ⌋ \forall n \in N^{*},|\left \{ \left \lfloor \frac{n}{d} \right \rfloor | d \in N^{*},d \leq n\right \} | \leq \left \lfloor 2\sqrt{n} \right \rfloor ∀n∈N∗,∣{⌊dn⌋∣d∈N∗,d≤n}∣≤⌊2n⌋
证明:
对于
d
≤
⌊
n
⌋
d \leq \left \lfloor \sqrt{n} \right \rfloor
d≤⌊n⌋,
⌊
n
d
⌋
\left \lfloor \frac{n}{d} \right \rfloor
⌊dn⌋ 最多有
⌊
n
⌋
\left \lfloor \sqrt{n} \right \rfloor
⌊n⌋ 种取值。
对于
d
>
⌊
n
⌋
d > \left \lfloor \sqrt{n} \right \rfloor
d>⌊n⌋,
⌊
n
d
⌋
\left \lfloor \frac{n}{d} \right \rfloor
⌊dn⌋ 最多有
⌊
n
⌋
\left \lfloor \sqrt{n} \right \rfloor
⌊n⌋ 种取值。
综上,的证。
数论分块结论(区间右端点公式)
对于常数 n n n,使得式子 ⌊ n i ⌋ = ⌊ n j ⌋ \left \lfloor \frac{n}{i} \right \rfloor = \left \lfloor \frac{n}{j} \right \rfloor ⌊in⌋=⌊jn⌋ 成立,且 i ≤ j ≤ n i \leq j \leq n i≤j≤n 的最大的 j j j 为 ⌊ n ⌊ n i ⌋ ⌋ \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor ⌊⌊in⌋n⌋。即 i i i 所在的区间的右端点为 ⌊ n ⌊ n i ⌋ ⌋ \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor ⌊⌊in⌋n⌋。
证明:
设
⌊
n
i
⌋
=
k
\left \lfloor \frac{n}{i} \right \rfloor = k
⌊in⌋=k
则
j
×
k
≤
n
j \times k \leq n
j×k≤n
由于
j
∈
N
∗
j \in N^{*}
j∈N∗,则
j
≤
⌊
n
k
⌋
=
⌊
n
⌊
n
i
⌋
⌋
j \leq \left \lfloor \frac{n}{k} \right \rfloor = \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor
j≤⌊kn⌋=⌊⌊in⌋n⌋
得证。
过程
数论分块的过程大概如下:
考虑和式:
∑
i
=
1
n
f
(
i
)
g
(
⌊
n
i
⌋
)
\sum_{i = 1}^{n} f(i)g(\left \lfloor \frac{n}{i} \right \rfloor)
∑i=1nf(i)g(⌊in⌋)
那么由于
⌊
n
i
⌋
\left \lfloor \frac{n}{i} \right \rfloor
⌊in⌋ 呈现块状分布,相同的值对应的
i
i
i 是连续的一段。因此这一段内
g
(
⌊
n
i
⌋
)
g(\left \lfloor \frac{n}{i} \right \rfloor)
g(⌊in⌋) 也是相等的。我们求出一段的
f
f
f 和,乘上对应的
g
g
g,就能算出一段的和。然后根据 右端点公式 跳到下一段的开头即可。由引理
2
2
2 我们知道了总段数不会超过
⌊
2
n
⌋
\left \lfloor 2\sqrt{n} \right \rfloor
⌊2n⌋,因此复杂度是
O
(
n
)
O(\sqrt{n})
O(n) 的。
模版代码:
int calc(int n) {
int l = 1, r, res = 0;
while(l <= n) {
r = (n / (n / l)); // r为l所在段的右端点
res += (pre[r] - pre[l - 1]) * g[n / l]; // pre[i] = f[1] + f[2] + ... + f[i]
l = r + 1;
}
return res;
}
N N N 维数论分块
什么是多维数论分块?
我们首先考虑一个和式:
∑ i = 1 m i n ( n , m ) f ( i ) g ( ⌊ n i ⌋ ) h ( ⌊ m i ⌋ ) \sum_{i = 1}^{min(n, m)}f(i)g(\left \lfloor \frac{n}{i} \right \rfloor)h(\left \lfloor \frac{m}{i} \right \rfloor) ∑i=1min(n,m)f(i)g(⌊in⌋)h(⌊im⌋)
我们发现式子中分母有两种:
n
n
n 和
m
m
m。这时候我们把
⌊
n
i
⌋
\left \lfloor \frac{n}{i} \right \rfloor
⌊in⌋ 和
⌊
m
i
⌋
\left \lfloor \frac{m}{i} \right \rfloor
⌊im⌋ 用线段图画出来,一条线段内的
i
i
i 表示
⌊
n
i
⌋
\left \lfloor \frac{n}{i} \right \rfloor
⌊in⌋ 的值相等。如下图:
然后我们把两条线段的所有端点拿出来作为划分整个序列的断点,如下图:
不难发现,此时序列里每一段中
⌊
n
i
⌋
\left \lfloor \frac{n}{i} \right \rfloor
⌊in⌋ 都相等,
⌊
m
i
⌋
\left \lfloor \frac{m}{i} \right \rfloor
⌊im⌋ 也相等,因此我们又可以一次计算一整段的和了。但是要乘一个
2
2
2 的常数。(因为断点数乘
2
2
2,所以段数多了一倍)
上面的就算是 二维数论分块。维度数 实际上就是 不同的分子数。 n n n 维数论分块的复杂度是 O ( n n ) O(n\sqrt{n}) O(nn) 的。但是一般二维数论分块最常见。
二维数论分块模版:
int calc(int n, int m) {
int t = min(n, m);
int l = 1, r, res = 0;
while(l <= t) {
r = min({n / (n / l), m / (m / l), t});
res += (pre[r] - pre[l - 1]) * g[n / l] * h[m / l];
l = r + 1;
}
return res;
}
n n n 维数论分块模版:
int calc(int n) {
int t = a[1];
int l = 1, r, res = 0;
for(int i = 1; i <= n; i ++ ) t = min(t, a[i]);
while(l <= t) {
r = t;
for(int i = 1; i <= n; i ++ ) {
r = min(r, a[i] / (a[i] / l));
}
int tmp = pre[r] - pre[l - 1];
for(int i = 1; i <= n; i ++ ) {
tmp = tmp * f[i][a[i] / l];
}
res += tmp;
l = r + 1;
}
return res;
}
向上取整的数论分块
与向下取整十分相似。核心思路相同,但是需要重新推导右端点公式。
结论:
对于常数
n
n
n,使得
⌈
n
i
⌉
=
⌈
n
j
⌉
\left \lceil \frac{n}{i} \right \rceil = \left \lceil \frac{n}{j} \right \rceil
⌈in⌉=⌈jn⌉ 成立并且满足
i
≤
j
≤
n
i \leq j \leq n
i≤j≤n 的最大的
j
j
j 为
⌊
n
−
1
⌊
n
−
1
i
⌋
⌋
\left \lfloor \frac{n - 1}{\left \lfloor \frac{n - 1}{i} \right \rfloor} \right \rfloor
⌊⌊in−1⌋n−1⌋,即
i
i
i 所在块的右端点为
⌊
n
−
1
⌊
n
−
1
i
⌋
⌋
\left \lfloor \frac{n - 1}{\left \lfloor \frac{n - 1}{i} \right \rfloor} \right \rfloor
⌊⌊in−1⌋n−1⌋。
证明:
不难发现
⌈
n
i
⌉
=
⌊
n
−
1
i
⌋
+
1
\left \lceil \frac{n}{i} \right \rceil = \left \lfloor \frac{n - 1}{i} \right \rfloor + 1
⌈in⌉=⌊in−1⌋+1
这个可以讨论
n
n
n 是否是
i
i
i 的倍数来证明。
然后把
⌈
n
i
⌉
\left \lceil \frac{n}{i} \right \rceil
⌈in⌉ 和
⌈
n
j
⌉
\left \lceil \frac{n}{j} \right \rceil
⌈jn⌉ 分别换成
⌊
n
−
1
i
⌋
+
1
\left \lfloor \frac{n - 1}{i} \right \rfloor + 1
⌊in−1⌋+1 和
⌊
n
−
1
j
⌋
+
1
\left \lfloor \frac{n - 1}{j} \right \rfloor + 1
⌊jn−1⌋+1 然后就跟向下取整是一样的了。
注意:当 i = n i = n i=n 时会出现分母为 0 0 0 的情况,需要特殊处理。
例题
H ( n ) H(n) H(n)
题意: 给你 n n n,求 ∑ i = 1 n ⌊ n i ⌋ \sum_{i = 1}^{n} \left \lfloor \frac{n}{i} \right \rfloor ∑i=1n⌊in⌋。
分析:没什么好说的,数论分块板子题。
CODE:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int T, n;
LL calc(int n) {
int l = 1, r; LL res = 0;
while(l <= n) {
r = n / (n / l);
res += 1LL * (r - l + 1LL) * (n / l);
l = r + 1;
}
return res;
}
int main() {
scanf("%d", &T);
while(T -- ) {
scanf("%d", &n);
printf("%lld\n", calc(n));
}
return 0;
}
[CQOI2007] 余数求和
点这里
题意: 给出正整数
n
n
n,
k
k
k。求
G
(
n
,
k
)
=
∑
i
=
1
n
k
m
o
d
i
G(n, k) = \sum_{i = 1}^{n} k \ mod \ i
G(n,k)=∑i=1nk mod i。
1
≤
n
,
k
≤
1
0
9
1 \leq n,k \leq 10^9
1≤n,k≤109。
分析:
G
(
n
,
k
)
=
∑
i
=
1
n
k
−
i
×
⌊
k
i
⌋
=
n
×
k
−
∑
i
=
1
n
i
×
⌊
k
i
⌋
G(n, k) = \sum_{i = 1}^{n} k - i\times \left \lfloor \frac{k}{i} \right \rfloor = n\times k - \sum_{i = 1}^{n} i \times \left \lfloor \frac{k}{i} \right \rfloor
G(n,k)=∑i=1nk−i×⌊ik⌋=n×k−∑i=1ni×⌊ik⌋
然后一维数论分块即可。复杂度 O ( m i n ( n , k ) ) O(\sqrt{min(n, k)}) O(min(n,k))。
CODE:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, k, res;
int main() {
cin >> n >> k;
res = n * k;
LL l = 1, r;
while(l <= min(n, k)) {
r = min(n, k / (k / l));
res = res - (r - l + 1LL) * (l + r) / 2LL * (k / l);
l = r + 1LL;
}
cout << res << endl;
return 0;
}
[清华集训2012] 模积和
点这里
题意: 求 ∑ i = 1 n ∑ j = 1 m ( n m o d i ) × ( m m o d j ) , i ≠ j \sum_{i = 1}^{n} \sum_{j = 1}^{m} (n \ mod \ i) \times (m \ mod \ j), i \ne j ∑i=1n∑j=1m(n mod i)×(m mod j),i=j。对 19940417 19940417 19940417 取模。
1 ≤ n , m ≤ 1 0 9 1 \leq n,m \leq 10^9 1≤n,m≤109
分析:
简单题。
首先通过容斥满足
i
≠
j
i \ne j
i=j 的限制,答案就是:
∑ i = 1 n ∑ j = 1 m ( n m o d i ) × ( m m o d j ) − s u m i = 1 m i n ( n , m ) ( n m o d i ) × ( m m o d i ) \sum_{i = 1}^{n} \sum_{j = 1}^{m}(n \ mod \ i) \times (m \ mod \ j) - sum_{i = 1}^{min(n, m)}(n \ mod \ i) \times (m \ mod \ i) ∑i=1n∑j=1m(n mod i)×(m mod j)−sumi=1min(n,m)(n mod i)×(m mod i)
分开开两个式子。
对于左边:
∑
i
=
1
n
∑
j
=
1
m
(
n
m
o
d
i
)
×
(
m
m
o
d
j
)
\sum_{i = 1}^{n} \sum_{j = 1}^{m} (n \ mod \ i) \times (m \ mod \ j)
∑i=1n∑j=1m(n mod i)×(m mod j)
=
∑
i
=
1
n
∑
j
=
1
m
(
n
−
i
×
⌊
n
i
⌋
)
×
(
m
−
j
×
⌊
m
j
⌋
)
= \sum_{i = 1}^{n}\sum_{j = 1}^{m}(n - i\times \left \lfloor \frac{n}{i} \right \rfloor) \times (m - j\times \left \lfloor \frac{m}{j} \right \rfloor)
=∑i=1n∑j=1m(n−i×⌊in⌋)×(m−j×⌊jm⌋)
=
s
u
m
i
=
1
n
∑
j
=
1
m
n
m
−
n
j
×
⌊
m
j
⌋
−
m
i
×
⌊
n
i
⌋
+
i
j
×
⌊
n
i
⌋
×
⌊
m
j
⌋
= sum_{i = 1}^{n}\sum_{j = 1}^{m} nm - nj \times \left \lfloor \frac{m}{j} \right \rfloor - mi \times \left \lfloor \frac{n}{i} \right \rfloor + ij \times \left \lfloor \frac{n}{i} \right \rfloor \times \left \lfloor \frac{m}{j} \right \rfloor
=sumi=1n∑j=1mnm−nj×⌊jm⌋−mi×⌊in⌋+ij×⌊in⌋×⌊jm⌋
=
n
2
m
2
−
n
2
∑
j
=
1
m
j
×
⌊
m
j
⌋
−
m
2
∑
i
=
1
n
i
×
⌊
n
i
⌋
+
∑
i
=
1
n
(
i
×
⌊
n
i
⌋
)
×
∑
j
=
1
m
(
j
×
⌊
m
j
⌋
)
=n^2m^2 -n^2\sum_{j = 1}^{m}j\times \left \lfloor \frac{m}{j} \right \rfloor - m^2\sum_{i = 1}^{n}i\times \left \lfloor \frac{n}{i} \right \rfloor + \sum_{i = 1}^{n}(i \times\left \lfloor \frac{n}{i} \right \rfloor) \times \sum_{j = 1}^{m}(j \times \left \lfloor \frac{m}{j} \right \rfloor)
=n2m2−n2∑j=1mj×⌊jm⌋−m2∑i=1ni×⌊in⌋+∑i=1n(i×⌊in⌋)×∑j=1m(j×⌊jm⌋)
化简到这一步后,显然每一个求和符号都可以用数论分块在 O ( n ) O(\sqrt{n}) O(n) 的时间内求出。
再看右边:
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
m
o
d
i
)
×
(
m
m
o
d
i
)
\sum_{i = 1}^{min(n, m)}(n \ mod \ i) \times (m \ mod \ i)
∑i=1min(n,m)(n mod i)×(m mod i)
=
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
−
i
×
⌊
n
i
⌋
)
×
(
m
−
i
×
⌊
m
i
⌋
)
= \sum_{i = 1}^{min(n, m)}(n - i \times\left \lfloor \frac{n}{i} \right \rfloor) \times (m - i \times \left \lfloor \frac{m}{i} \right \rfloor)
=∑i=1min(n,m)(n−i×⌊in⌋)×(m−i×⌊im⌋)
令
m
i
n
(
n
,
m
)
=
t
min(n, m) = t
min(n,m)=t,则原式
=
∑
i
=
1
t
n
m
−
n
i
×
⌊
m
i
⌋
−
m
i
×
⌊
n
i
⌋
+
i
2
×
⌊
n
i
⌋
×
⌊
m
i
⌋
=\sum_{i = 1}^{t}nm - ni \times \left \lfloor \frac{m}{i} \right \rfloor - mi \times \left \lfloor \frac{n}{i} \right \rfloor + i^2 \times \left \lfloor \frac{n}{i} \right \rfloor \times \left \lfloor \frac{m}{i} \right \rfloor
=∑i=1tnm−ni×⌊im⌋−mi×⌊in⌋+i2×⌊in⌋×⌊im⌋
=
n
m
t
−
n
×
∑
i
=
1
t
i
×
⌊
m
i
⌋
−
m
×
∑
i
=
1
t
i
×
⌊
m
i
⌋
+
∑
i
=
1
t
i
2
×
⌊
m
i
⌋
×
⌊
n
i
⌋
=nmt - n\times \sum_{i = 1}^{t}i\times \left \lfloor \frac{m}{i} \right \rfloor - m \times \sum_{i = 1}^{t} i \times \left \lfloor \frac{m}{i} \right \rfloor + \sum_{i = 1}^{t} i^2 \times \left \lfloor \frac{m}{i} \right \rfloor \times \left \lfloor \frac{n}{i} \right \rfloor
=nmt−n×∑i=1ti×⌊im⌋−m×∑i=1ti×⌊im⌋+∑i=1ti2×⌊im⌋×⌊in⌋
然后就是前两个求和符号非常简单,直接一维整除分块即可。最后一个求和符号需要二维整除分块,并且还需要快速计算出 ∑ i = l r i 2 \sum_{i = l}^{r} i^2 ∑i=lri2。
记
f
(
r
)
=
∑
i
=
1
r
i
2
f(r) = \sum_{i = 1}^{r} i^2
f(r)=∑i=1ri2,那么
s
u
m
i
=
l
r
i
2
=
f
(
r
)
−
f
(
l
−
1
)
sum_{i = l}^{r}i^2 = f(r) - f(l - 1)
sumi=lri2=f(r)−f(l−1)。然后就是有一个公式:
∑
i
=
1
n
i
2
=
n
×
(
n
+
1
)
×
(
2
n
+
1
)
6
\sum_{i = 1}^{n}i^2 = \frac{n \times (n + 1) \times (2n + 1)}{6}
∑i=1ni2=6n×(n+1)×(2n+1)。有了这个我们就可以
O
(
1
)
O(1)
O(1) 求出
f
f
f 了,这样这个题就做完了。
时间复杂度 O ( n ) O(\sqrt{n}) O(n)
CODE:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 19940417;
LL n, m, res;
inline LL get(LL x, LL y) { // x 是循环上界,y是分子
LL l = 1, r; LL res = 0;
while(l <= x) {
r = min(x, y / (y / l));
res = (res + 1LL * ((l + r) * (r - l + 1LL) / 2LL) % mod * (y / l) % mod) % mod;
l = r + 1LL;
}
return res;
}
LL solve(LL n) { // \sum_{i = 1}^{n} i^2
LL x = n, y = n + 1LL, z = 2LL * n + 1LL;
if(x % 2 == 0) x /= 2LL;
else if(y % 2 == 0) y /= 2LL;
else z /= 2LL;
if(x % 3 == 0) x /= 3LL;
else if(y % 3 == 0) y /= 3LL;
else z /= 3LL;
return x * y % mod * z % mod;
}
inline LL calc(LL t, LL n, LL m) { // 二维数论分块, t是上界
LL l = 1, r; LL res = 0;
while(l <= t) {
r = min({t, n / (n / l), m / (m / l)});
res = (res + ((solve(r) - solve(l - 1)) % mod + mod) % mod * (n / l) % mod * (m / l) % mod) % mod;
l = r + 1LL;
}
return res;
}
int main() {
cin >> n >> m;
LL t1 = ((m * m % mod - get(m, m)) % mod + mod) % mod;
LL t2 = ((n * n % mod - get(n, n)) % mod + mod) % mod;
res = t1 * t2 % mod;
LL t = min(n, m);
LL del = n * m % mod * t % mod;
del = ((del - n * get(t, m) % mod) % mod + mod) % mod;
del = ((del - m * get(t, n) % mod) % mod + mod) % mod;
del = (del + calc(t, n, m)) % mod;
res = ((res - del) % mod + mod) % mod;
cout << res << endl;
return 0;
}