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

算法

概述

数论分块可以快速计算一些含有除法向下取整的和式(即形如 ∑ 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(n log2n) 的复杂度内计算出上述和式的值。

它主要利用了富比尼定理(其实没啥用,不需要懂)。核心想法是 ⌊ 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,cZbca=cba

证明:
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=cba+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(0q<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 cba+cr=p+cq+r=p=cba

引理 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 nN,{dndN,dn}2n

证明:
对于 d ≤ ⌊ n ⌋ d \leq \left \lfloor \sqrt{n} \right \rfloor dn ⌊ 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 ijn 的最大的 j j j ⌊ n ⌊ n i ⌋ ⌋ \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor inn。即 i i i 所在的区间的右端点为 ⌊ n ⌊ n i ⌋ ⌋ \left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor inn

证明:
⌊ n i ⌋ = k \left \lfloor \frac{n}{i} \right \rfloor = k in=k
j × k ≤ n j \times k \leq n j×kn
由于 j ∈ N ∗ j \in N^{*} jN,则 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 jkn=inn

得证。

过程

数论分块的过程大概如下:
考虑和式: ∑ 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 ijn 的最大的 j j j ⌊ n − 1 ⌊ n − 1 i ⌋ ⌋ \left \lfloor \frac{n - 1}{\left \lfloor \frac{n - 1}{i} \right \rfloor} \right \rfloor in1n1,即 i i i 所在块的右端点为 ⌊ n − 1 ⌊ n − 1 i ⌋ ⌋ \left \lfloor \frac{n - 1}{\left \lfloor \frac{n - 1}{i} \right \rfloor} \right \rfloor in1n1

证明:

不难发现 ⌈ n i ⌉ = ⌊ n − 1 i ⌋ + 1 \left \lceil \frac{n}{i} \right \rceil = \left \lfloor \frac{n - 1}{i} \right \rfloor + 1 in=in1+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 in1+1 ⌊ n − 1 j ⌋ + 1 \left \lfloor \frac{n - 1}{j} \right \rfloor + 1 jn1+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=1nin

分析:没什么好说的,数论分块板子题。
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 1n,k109

分析:
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=1nki×ik=n×ki=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=1nj=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 1n,m109

分析:
简单题。
首先通过容斥满足 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=1nj=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=1nj=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=1nj=1m(ni×in)×(mj×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=1nj=1mnmnj×jmmi×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) =n2m2n2j=1mj×jmm2i=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)(ni×in)×(mi×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=1tnmni×immi×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 =nmtn×i=1ti×imm×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(l1)。然后就是有一个公式:
∑ 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;
}
  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值