Description
数论分块,通常用于快速求解形如 ∑ i = 1 n f ( i ) ⋅ g ( ⌊ n i ⌋ ) \sum\limits_{i=1}^n f(i) \cdot g\left(\left\lfloor\frac{n}{i}\right\rfloor\right) i=1∑nf(i)⋅g(⌊in⌋) 的和式,所以通常被称为 整除分块,当能用 O ( 1 ) O(1) O(1) 计算出 ∑ i = l r f ( i ) \sum\limits_{i=l}^rf(i) i=l∑rf(i) 时,数论分块便能用 O ( n ) O(\sqrt n) O(n) 的时间计算出上式的值、数论分块经常搭配 莫比乌斯反演 一起使用。
Conclusion
∀
n
,
l
∈
N
∗
,
l
≤
n
\forall n,l\in \mathbb{N}^*,l\le n
∀n,l∈N∗,l≤n,使得
⌊
n
l
⌋
=
⌊
n
r
⌋
\left\lfloor\dfrac{n}{l}\right\rfloor=\left\lfloor\dfrac{n}{r}\right\rfloor
⌊ln⌋=⌊rn⌋
成立的最大的满足
l
≤
r
≤
n
l\le r\le n
l≤r≤n 的
r
r
r 的值为
⌊
n
⌊
n
l
⌋
⌋
\left\lfloor\dfrac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor
⌊⌊ln⌋n⌋。
也就是说,若某个块内所有数的值为 ⌊ n l ⌋ = k \left\lfloor\dfrac{n}{l}\right\rfloor = k ⌊ln⌋=k,那么这个块的右端点就是 r = ⌊ n k ⌋ r=\left\lfloor\dfrac{n}{k}\right\rfloor r=⌊kn⌋。
Proof
对于这个块中的任意一个数 x x x,应当满足 ⌊ n x ⌋ = k \left\lfloor\dfrac{n}{x}\right\rfloor = k ⌊xn⌋=k,即 n = x k + r ( 0 ≤ r < x ) n=xk+r(0\le r < x) n=xk+r(0≤r<x)。
当
n
,
k
n,k
n,k 已知时,只要确定
x
x
x,就有一个
r
r
r 与之对应。
n
=
x
k
+
r
n
≥
x
k
x
≤
n
k
n = xk + r \\ n \ge xk \\ x \le \dfrac{n}{k}
n=xk+rn≥xkx≤kn
即
x
x
x 的最大值为
⌊
n
k
⌋
\left\lfloor\dfrac{n}{k}\right\rfloor
⌊kn⌋。
即 r = ⌊ n ⌊ n l ⌋ ⌋ r = \left\lfloor\dfrac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor r=⌊⌊ln⌋n⌋。
证毕。
Method
一个块内的所有数都相等,所以每块每块地求和即可。
在找到上一个块的右端点后,加一就可以得到下一个块的左端点。
模板题,求 ∑ i = 1 n ⌊ n i ⌋ \sum\limits_{i=1}^n \left\lfloor\dfrac{n}{i}\right\rfloor i=1∑n⌊in⌋。
//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#define Debug(x) cout << #x << "=" << x << endl
#define int long long
using namespace std;
int H(int n)
{
int res = 0;
for (int l = 1, r; l <= n; l = r + 1)
{
int k = n / l;
r = n / k;
res += k * (r - l + 1);
}
return res;
}
signed main()
{
int t;
scanf("%lld", &t);
while (t--)
{
int n;
scanf("%lld", &n);
printf("%lld\n", H(n));
}
return 0;
}
Complexity
Lemma
∀ n , i ∈ N ∗ , i ≤ n \forall n,i \in \mathbb{N}^*,i\le n ∀n,i∈N∗,i≤n, ⌊ n i ⌋ \left\lfloor\dfrac{n}{i}\right\rfloor ⌊in⌋ 的不同值最多有 2 ⌊ n ⌋ 2\left\lfloor\sqrt{n}\right\rfloor 2⌊n⌋ 个,即最多有 2 ⌊ n ⌋ 2\left\lfloor\sqrt{n}\right\rfloor 2⌊n⌋ 个块。
Proof
∀ i ≤ ⌊ n ⌋ \forall \, i\le \left\lfloor\sqrt{n}\right\rfloor ∀i≤⌊n⌋, i i i 只有 ⌊ n ⌋ \left\lfloor\sqrt{n}\right\rfloor ⌊n⌋ 种取值,则 ⌊ n i ⌋ \left\lfloor\dfrac{n}{i}\right\rfloor ⌊in⌋ 只有至多 ⌊ n ⌋ \left\lfloor\sqrt{n}\right\rfloor ⌊n⌋ 种取值;
∀ i > ⌊ n ⌋ \forall \, i > \left\lfloor\sqrt{n}\right\rfloor ∀i>⌊n⌋,有 i ≥ ⌊ n ⌋ + 1 > n i \ge \left\lfloor\sqrt{n}\right\rfloor + 1 > \sqrt{n} i≥⌊n⌋+1>n, ⌊ n i ⌋ ≤ ⌊ n n ⌋ = ⌊ n ⌋ \left\lfloor\dfrac{n}{i}\right\rfloor \le \left\lfloor\dfrac{n}{\sqrt{n}}\right\rfloor = \left\lfloor\sqrt{n}\right\rfloor ⌊in⌋≤⌊nn⌋=⌊n⌋,也只有至多 ⌊ n ⌋ \left\lfloor\sqrt{n}\right\rfloor ⌊n⌋ 种取值。
所以最多只有 2 ⌊ n ⌋ 2\left\lfloor\sqrt{n}\right\rfloor 2⌊n⌋ 种取值。
证毕。
由引理可知最多有 2 ⌊ n ⌋ 2\left\lfloor\sqrt{n}\right\rfloor 2⌊n⌋ 个块,即 for \operatorname{for} for 循环最多会执行 2 ⌊ n ⌋ 2\left\lfloor\sqrt{n}\right\rfloor 2⌊n⌋ 次,所以时间复杂度为 O ( n ) O(\sqrt{n}) O(n)。
Extension
N N N 维数论分块。
求形如 ∑ i = 1 n ⌊ a 1 i ⌋ ⌊ a 2 i ⌋ ⋯ ⌊ a m i ⌋ \sum\limits_{i=1}^n \left\lfloor\dfrac{a_1}{i}\right\rfloor\left\lfloor\dfrac{a_2}{i}\right\rfloor \cdots \left\lfloor\dfrac{a_m}{i}\right\rfloor i=1∑n⌊ia1⌋⌊ia2⌋⋯⌊iam⌋ 的和式的值。
r = min i = 1 m { ⌊ a i ⌊ a i l ⌋ ⌋ } r=\min\limits_{i=1}^m\left\{\left\lfloor\dfrac{a_i}{\left\lfloor\frac{a_i}{l}\right\rfloor}\right\rfloor\right\} r=i=1minm{⌊⌊lai⌋ai⌋} 即可,即对于每一个块的右端点取最小(最接近左端点)的那个作为整体的右端点。
较常用的是
2
2
2 维数论分块。求
∑
i
=
1
n
⌊
n
i
⌋
⌊
m
i
⌋
\sum\limits_{i=1}^n \left\lfloor\dfrac{n}{i}\right\rfloor \left\lfloor\dfrac{m}{i}\right\rfloor
i=1∑n⌊in⌋⌊im⌋ 时
r
=
min
(
⌊
n
⌊
n
l
⌋
⌋
,
⌊
m
⌊
m
l
⌋
⌋
)
r=\min\left(\left\lfloor\dfrac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor, \left\lfloor\dfrac{m}{\left\lfloor\frac{m}{l}\right\rfloor}\right\rfloor\right)
r=min(⌊⌊ln⌋n⌋,⌊⌊lm⌋m⌋),也就是在代码中 r = min(n / (n / l), m / (m / l));
。
Problems
A
Description
计算 ∑ i = 1 n k m o d i \sum\limits_{i=1}^n k\bmod i i=1∑nkmodi。
Solution
∑ i = 1 n k m o d i = ∑ i = 1 n k − ⌊ k i ⌋ i = n k − ∑ i = 1 n ⌊ k i ⌋ i \begin{aligned} \sum\limits_{i=1}^n k\bmod i & = \sum\limits_{i=1}^n k - \left\lfloor\frac{k}{i}\right\rfloor i \\ & = nk - \sum\limits_{i=1}^n \left\lfloor\frac{k}{i}\right\rfloor i \\ \end{aligned} i=1∑nkmodi=i=1∑nk−⌊ik⌋i=nk−i=1∑n⌊ik⌋i
观察后面一项,对于左端点为 l l l,右端点为 r r r 的块,贡献就是
∑
i
=
l
r
⌊
k
i
⌋
i
=
∑
i
=
l
r
⌊
k
l
⌋
i
=
⌊
k
l
⌋
∑
i
=
1
r
i
=
⌊
k
l
⌋
(
l
+
r
)
(
r
−
l
+
1
)
2
\begin{aligned} \sum\limits_{i=l}^r \left\lfloor\dfrac{k}{i}\right\rfloor i & = \sum\limits_{i=l}^r \left\lfloor\dfrac{k}{l}\right\rfloor i \\ & = \left\lfloor\dfrac{k}{l}\right\rfloor \sum\limits_{i=1}^r i \\ & = \left\lfloor\dfrac{k}{l}\right\rfloor \dfrac{(l+r)(r-l+1)}{2} \end{aligned}
i=l∑r⌊ik⌋i=i=l∑r⌊lk⌋i=⌊lk⌋i=1∑ri=⌊lk⌋2(l+r)(r−l+1)
但是这次
i
i
i 要循环到
n
n
n 而非
k
k
k,当
n
>
k
n > k
n>k 时
⌊
k
l
⌋
\left\lfloor\dfrac{k}{l}\right\rfloor
⌊lk⌋ 有可能为
0
0
0,这样
r
=
⌊
k
⌊
k
l
⌋
⌋
r = \left\lfloor\dfrac{k}{\left\lfloor\dfrac{k}{l}\right\rfloor}\right\rfloor
r=⎣⎢⎢⎢⎢⎢⌊lk⌋k⎦⎥⎥⎥⎥⎥ 就无意义了。
发现当 i > k i > k i>k 时 ⌊ k i ⌋ i = 0 \left\lfloor\dfrac{k}{i}\right\rfloor i = 0 ⌊ik⌋i=0,所以 i i i 循环到 min ( n , k ) \min(n,k) min(n,k) 即可。
在取右端点时 r = min ( n , ⌊ k ⌊ k l ⌋ ⌋ ) r = \min\left(n, \left\lfloor\dfrac{k}{\left\lfloor\dfrac{k}{l}\right\rfloor}\right\rfloor\right) r=min⎝⎜⎜⎛n,⎣⎢⎢⎢⎢⎢⌊lk⌋k⎦⎥⎥⎥⎥⎥⎠⎟⎟⎞。
Code
//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;
ll block(int n, int k)
{
ll res = 0;
for (int l = 1, r; l <= min(n, k); l = r + 1)
{
r = min(n, k / (k / l));
res += (ll)(k / l) * ((ll)(l + r) * (r - l + 1) >> 1);
}
return res;
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
printf("%lld\n", (ll)n * k - block(n, k));
return 0;
}
B
Description
求
[
∑
i
=
1
n
∑
j
=
1
m
(
n
m
o
d
i
)
(
m
m
o
d
j
)
,
i
≠
j
]
m
o
d
19940417
\left[\sum\limits_{i=1}^n \sum\limits_{j=1}^m (n \bmod i)(m \bmod j), i \ne j\right]\bmod 19940417
[i=1∑nj=1∑m(nmodi)(mmodj),i=j]mod19940417
Solution
假设
n
≤
m
n \le m
n≤m(否则交换)。
a
n
s
=
∑
i
=
1
n
∑
j
=
1
m
(
n
m
o
d
i
)
(
m
m
o
d
j
)
−
∑
i
=
1
n
(
n
m
o
d
i
)
(
m
m
o
d
i
)
=
∑
i
=
1
n
n
m
o
d
i
⋅
∑
j
=
1
m
m
m
o
d
j
−
∑
i
=
1
n
(
n
m
o
d
i
)
(
m
m
o
d
i
)
\begin{aligned} ans & = \sum\limits_{i=1}^n \sum\limits_{j=1}^m (n\bmod i)(m\bmod j) - \sum\limits_{i=1}^n (n\bmod i)(m\bmod i) \\ & = \sum\limits_{i=1}^n n\bmod i \cdot \sum\limits_{j=1}^m m\bmod j - \sum\limits_{i=1}^n (n\bmod i)(m \bmod i) \end{aligned}
ans=i=1∑nj=1∑m(nmodi)(mmodj)−i=1∑n(nmodi)(mmodi)=i=1∑nnmodi⋅j=1∑mmmodj−i=1∑n(nmodi)(mmodi)
前面两项都形如
∑
i
=
1
k
k
m
o
d
i
\sum\limits_{i=1}^k k\bmod i
i=1∑kkmodi,用上一题的思路求解。
∑
i
=
1
n
(
n
m
o
d
i
)
(
m
m
o
d
i
)
=
∑
i
=
1
n
(
n
−
⌊
n
i
⌋
i
)
(
m
−
⌊
m
i
⌋
i
)
=
∑
i
=
1
n
(
n
m
−
⌊
n
i
⌋
i
⋅
m
−
⌊
m
i
⌋
i
⋅
n
+
⌊
n
i
⌋
⌊
m
i
⌋
i
2
)
=
n
2
m
−
m
∑
i
=
1
n
⌊
n
i
⌋
i
−
n
∑
i
=
1
n
⌊
m
i
⌋
i
+
∑
i
=
1
n
⌊
n
i
⌋
⌊
m
i
⌋
i
2
\begin{aligned} \sum\limits_{i=1}^n (n\bmod i)(m\bmod i) & = \sum\limits_{i=1}^n (n - \left\lfloor\dfrac{n}{i}\right\rfloor i)(m - \left\lfloor\dfrac{m}{i}\right\rfloor i) \\ & = \sum\limits_{i=1}^n (nm - \left\lfloor\dfrac{n}{i}\right\rfloor i\cdot m - \left\lfloor\dfrac{m}{i}\right\rfloor i\cdot n + \left\lfloor\dfrac{n}{i}\right\rfloor \left\lfloor\dfrac{m}{i}\right\rfloor i ^ 2) \\ & = n ^ 2 m - m\sum\limits_{i=1}^n \left\lfloor\dfrac{n}{i}\right\rfloor i - n \sum\limits_{i=1}^n \left\lfloor\dfrac{m}{i}\right\rfloor i + \sum\limits_{i=1}^n \left\lfloor\dfrac{n}{i}\right\rfloor \left\lfloor\dfrac{m}{i}\right\rfloor i ^ 2 \end{aligned}
i=1∑n(nmodi)(mmodi)=i=1∑n(n−⌊in⌋i)(m−⌊im⌋i)=i=1∑n(nm−⌊in⌋i⋅m−⌊im⌋i⋅n+⌊in⌋⌊im⌋i2)=n2m−mi=1∑n⌊in⌋i−ni=1∑n⌊im⌋i+i=1∑n⌊in⌋⌊im⌋i2
第
2
,
3
2,3
2,3 项都是模板,第
4
4
4 项就是一个扩展版中的
2
2
2 维数论分块,对于左端点为
l
l
l,右端点为
r
r
r 的块,贡献就是
∑
i
=
l
r
⌊
n
i
⌋
⌊
m
i
⌋
i
2
=
⌊
n
l
⌋
⌊
m
l
⌋
∑
i
=
l
r
i
2
=
⌊
n
l
⌋
⌊
m
l
⌋
(
∑
i
=
1
r
i
2
−
∑
i
=
1
l
−
1
i
2
)
\begin{aligned} \sum\limits_{i=l}^r \left\lfloor\dfrac{n}{i}\right\rfloor \left\lfloor\dfrac{m}{i}\right\rfloor i ^ 2 & = \left\lfloor\dfrac{n}{l}\right\rfloor \left\lfloor\dfrac{m}{l}\right\rfloor \sum\limits_{i=l}^r i ^ 2 \\ & = \left\lfloor\dfrac{n}{l}\right\rfloor \left\lfloor\dfrac{m}{l}\right\rfloor (\sum\limits_{i=1}^r i ^ 2 - \sum\limits_{i=1}^{l-1} i ^ 2) \\ \end{aligned}
i=l∑r⌊in⌋⌊im⌋i2=⌊ln⌋⌊lm⌋i=l∑ri2=⌊ln⌋⌊lm⌋(i=1∑ri2−i=1∑l−1i2)
平方和有公式
∑
i
=
1
n
i
2
=
n
(
n
+
1
)
(
2
n
+
1
)
6
\sum\limits_{i=1}^n i ^ 2 = \dfrac{n(n+1)(2n+1)}{6}
i=1∑ni2=6n(n+1)(2n+1)。
Code
函数 block1 ( n , m ) \operatorname{block1}(n,m) block1(n,m) 求的是 ∑ i = 1 n ⌊ m i ⌋ i \sum\limits_{i=1}^n \left\lfloor\dfrac{m}{i}\right\rfloor i i=1∑n⌊im⌋i(其中保证 n ≤ m n \le m n≤m)。
函数 block2 ( n , m ) \operatorname{block2}(n,m) block2(n,m) 求的是 ∑ i = 1 n ⌊ n i ⌋ ⌊ m i ⌋ i 2 \sum\limits_{i=1}^n \left\lfloor\dfrac{n}{i}\right\rfloor \left\lfloor\dfrac{m}{i}\right\rfloor i ^ 2 i=1∑n⌊in⌋⌊im⌋i2。
//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#define Debug(x) cout << #x << "=" << x << endl
#define int long long
using namespace std;
const int MOD = 19940417;
int x, y;
void exgcd(int a, int b)
{
if (!b)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b);
int tmp = x;
x = y;
y = tmp - a / b * y;
}
int inv(int a)
{
exgcd(a, MOD);
x = (x % MOD + MOD) % MOD;
return x;
}
int block1(int n, int m)
{
int res = 0;
for (int l = 1, r; l <= n; l = r + 1)
{
r = min(n, m / (m / l));
res = (res + (m / l) * ((l + r) * (r - l + 1) / 2 % MOD) % MOD) % MOD;
}
return res;
}
int part1(int n, int m)
{
return ((n * n % MOD - block1(n, n)) * (m * m % MOD - block1(m, m)) % MOD + MOD) % MOD;
}
int sum(int n)
{
return n * (n + 1) % MOD * (2 * n + 1) % MOD * inv(6) % MOD;
}
int block2(int n, int m)
{
int res = 0;
for (int l = 1, r; l <= n; l = r + 1)
{
r = min(n / (n / l), m / (m / l));
res = (res + (n / l) * (m / l) % MOD * (sum(r) - sum(l - 1)) % MOD) % MOD;
}
return res;
}
int part2(int n, int m)
{
int a = n * n % MOD * m % MOD, b = block1(n, n) * m % MOD, c = block1(n, m) * n % MOD, d = block2(n, m);
return ((a - b - c + d) % MOD + MOD) % MOD;
}
signed main()
{
int n, m;
scanf("%lld%lld", &n, &m);
if (n > m)
{
swap(n, m);
}
printf("%lld\n", ((part1(n, m) - part2(n, m)) % MOD + MOD) % MOD);
return 0;
}
Reference
- [1] OI Wiki:数论分块
- [2] *ACoder*:总结与思考:数论分块