学习笔记 ---- 数论分块(整除分块)

算法

概述

数论分块可以快速计算一些含有除法向下取整的和式(即形如 \(\sum_{i = 1}^{n}f(i)g(\left \lfloor \frac{n}{i}\right \rfloor)\))。当可以在 \(O(1)\) 或者 \(O(log_2n)\) 的复杂度内计算 \(f(r) - f(l)\)时,数论分块就能在 \(O(\sqrt{n})\)\(O(\sqrt{n}log_2n)\) 的复杂度内计算出上述和式的值。

它主要利用了富比尼定理(其实没啥用,不需要懂)。核心想法是 \(\left \lfloor \frac{n}{i} \right \rfloor\) 相同的数打包同时计算

引理

引理\(1\)

\(\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\)

证明:
\(\frac{a}{b} = \left \lfloor \frac{a}{b} \right \rfloor + r(0 < r < 1)\)
\(\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\)
\(\left \lfloor \frac{a}{b} \right \rfloor = pc + q(0 \leq q<c)\)
\(q + r < 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\)

引理 \(2\)

\(\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\)

证明:
对于 \(d \leq \left \lfloor \sqrt{n} \right \rfloor\)\(\left \lfloor \frac{n}{d} \right \rfloor\) 最多有 \(\left \lfloor \sqrt{n} \right \rfloor\) 种取值。
对于 \(d > \left \lfloor \sqrt{n} \right \rfloor\)\(\left \lfloor \frac{n}{d} \right \rfloor\) 最多有 \(\left \lfloor \sqrt{n} \right \rfloor\) 种取值。

综上,的证。

数论分块结论(区间右端点公式)

对于常数 \(n\),使得式子 \(\left \lfloor \frac{n}{i} \right \rfloor = \left \lfloor \frac{n}{j} \right \rfloor\) 成立,且 \(i \leq j \leq n\) 的最大的 \(j\)\(\left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor\)。即 \(i\) 所在的区间的右端点为 \(\left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor\)

证明:
\(\left \lfloor \frac{n}{i} \right \rfloor = k\)
\(j \times k \leq n\)
由于 \(j \in N^{*}\),则 \(j \leq \left \lfloor \frac{n}{k} \right \rfloor = \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor} \right \rfloor\)

得证。

过程

数论分块的过程大概如下:
考虑和式:\(\sum_{i = 1}^{n} f(i)g(\left \lfloor \frac{n}{i} \right \rfloor)\)
那么由于 \(\left \lfloor \frac{n}{i} \right \rfloor\) 呈现块状分布,相同的值对应的 \(i\) 是连续的一段。因此这一段内 \(g(\left \lfloor \frac{n}{i} \right \rfloor)\) 也是相等的。我们求出一段的 \(f\) 和,乘上对应的 \(g\),就能算出一段的和。然后根据 右端点公式 跳到下一段的开头即可。由引理\(2\) 我们知道了总段数不会超过 \(\left \lfloor 2\sqrt{n} \right \rfloor\),因此复杂度是 \(O(\sqrt{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\) 维数论分块

什么是多维数论分块?
我们首先考虑一个和式:

\(\sum_{i = 1}^{min(n, m)}f(i)g(\left \lfloor \frac{n}{i} \right \rfloor)h(\left \lfloor \frac{m}{i} \right \rfloor)\)

我们发现式子中分母有两种: \(n\)\(m\)。这时候我们把 \(\left \lfloor \frac{n}{i} \right \rfloor\)\(\left \lfloor \frac{m}{i} \right \rfloor\) 用线段图画出来,一条线段内的 \(i\) 表示 \(\left \lfloor \frac{n}{i} \right \rfloor\) 的值相等。如下图:

1

然后我们把两条线段的所有端点拿出来作为划分整个序列的断点,如下图:

2

不难发现,此时序列里每一段中 \(\left \lfloor \frac{n}{i} \right \rfloor\) 都相等, \(\left \lfloor \frac{m}{i} \right \rfloor\) 也相等,因此我们又可以一次计算一整段的和了。但是要乘一个 \(2\) 的常数。(因为断点数乘 \(2\),所以段数多了一倍)

上面的就算是 二维数论分块维度数 实际上就是 不同的分子数\(n\) 维数论分块的复杂度是 \(O(n\sqrt{n})\) 的。但是一般二维数论分块最常见。

二维数论分块模版:

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\) 维数论分块模版:

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\),使得 \(\left \lceil \frac{n}{i} \right \rceil = \left \lceil \frac{n}{j} \right \rceil\) 成立并且满足 \(i \leq j \leq n\) 的最大的 \(j\)\(\left \lfloor \frac{n - 1}{\left \lfloor \frac{n - 1}{i} \right \rfloor} \right \rfloor\),即 \(i\) 所在块的右端点为 \(\left \lfloor \frac{n - 1}{\left \lfloor \frac{n - 1}{i} \right \rfloor} \right \rfloor\)

证明:

不难发现 \(\left \lceil \frac{n}{i} \right \rceil = \left \lfloor \frac{n - 1}{i} \right \rfloor + 1\)
这个可以讨论 \(n\) 是否是 \(i\) 的倍数来证明。
然后把 \(\left \lceil \frac{n}{i} \right \rceil\)\(\left \lceil \frac{n}{j} \right \rceil\) 分别换成 \(\left \lfloor \frac{n - 1}{i} \right \rfloor + 1\)\(\left \lfloor \frac{n - 1}{j} \right \rfloor + 1\) 然后就跟向下取整是一样的了。

注意:当 \(i = n\) 时会出现分母为 \(0\) 的情况,需要特殊处理。

例题

\(H(n)\)

题意: 给你 \(n\),求 \(\sum_{i = 1}^{n} \left \lfloor \frac{n}{i} \right \rfloor\)

分析:没什么好说的,数论分块板子题。
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\)\(k\)。求 \(G(n, k) = \sum_{i = 1}^{n} k \ mod \ i\)\(1 \leq n,k \leq 10^9\)

分析:
\(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\)

然后一维数论分块即可。复杂度 \(O(\sqrt{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] 模积和

点这里

题意: 求 \(\sum_{i = 1}^{n} \sum_{j = 1}^{m} (n \ mod \ i) \times (m \ mod \ j), i \ne j\)。对 \(19940417\) 取模。

\(1 \leq n,m \leq 10^9\)

分析:
简单题。
首先通过容斥满足 \(i \ne j\) 的限制,答案就是:

\(\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)\)

分开开两个式子。
对于左边:
\(\sum_{i = 1}^{n} \sum_{j = 1}^{m} (n \ mod \ i) \times (m \ mod \ 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)\)
\(= 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\)
\(=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)\)

化简到这一步后,显然每一个求和符号都可以用数论分块在 \(O(\sqrt{n})\) 的时间内求出。

再看右边:
\(\sum_{i = 1}^{min(n, m)}(n \ mod \ i) \times (m \ mod \ 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)\)
\(min(n, m) = t\),则原式
\(=\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\)
\(=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\)

然后就是前两个求和符号非常简单,直接一维整除分块即可。最后一个求和符号需要二维整除分块,并且还需要快速计算出 \(\sum_{i = l}^{r} i^2\)

\(f(r) = \sum_{i = 1}^{r} i^2\),那么\(sum_{i = l}^{r}i^2 = f(r) - f(l - 1)\)。然后就是有一个公式:
\(\sum_{i = 1}^{n}i^2 = \frac{n \times (n + 1) \times (2n + 1)}{6}\)。有了这个我们就可以 \(O(1)\) 求出 \(f\) 了,这样这个题就做完了。

时间复杂度 \(O(\sqrt{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;
}
posted @ 2026-09-12 20:54  Czl2007  阅读(7)  评论(0)    收藏  举报