1. 前言
整除分块,是一种数论的基础算法,必备知识,在很多题目中都有涉及但是题目很基础。
整除分块主要解决的是这样一类问题:求值:
∑ i = 1 n f ( i ) g ( ⌊ k i ⌋ ) \sum_{i=1}^{n}f(i)g(\Big\lfloor\dfrac{k}{i}\Big\rfloor) i=1∑nf(i)g(⌊ik⌋)
其中已知 f f f 的前缀和或者能 O ( 1 ) O(1) O(1) 计算 f ( r ) − f ( l ) f(r)-f(l) f(r)−f(l)。
正常的计算方式是 O ( n ) O(n) O(n) 的,但是一些题里面该复杂度不能接受,此时就需要整除分块,整除分块能做到 O ( n ) O(\sqrt{n}) O(n) 解决此类问题。
2. 详解
首先讨论这个问题的简化版本:
∑ i = 1 n ⌊ k i ⌋ \sum_{i=1}^{n}\Big\lfloor\dfrac{k}{i}\Big\rfloor i=1∑n⌊ik⌋
通过打表可以发现,对于 ⌊ k i ⌋ \Big\lfloor\dfrac{k}{i}\Big\rfloor ⌊ik⌋,容易有一个较大的区间 [ l , r ] [l,r] [l,r],满足 ∀ i , j ∈ [ l , r ] , ⌊ k i ⌋ = ⌊ k j ⌋ \forall i,j \in [l,r],\Big\lfloor\dfrac{k}{i}\Big\rfloor=\Big\lfloor\dfrac{k}{j}\Big\rfloor ∀i,j∈[l,r],⌊ik⌋=⌊jk⌋,下面定义一个满足上述条件的区间的值为 i ∈ [ l , r ] , ⌊ k i ⌋ i \in [l,r],\Big\lfloor\dfrac{k}{i}\Big\rfloor i∈[l,r],⌊ik⌋。
也就是说有连续段是相同的,因此整除分块需要考虑如何快速处理出这些区间,最好的方式当然就是将区间处理完之后,不同的区间的值不同。
其实数学上已经证明了一些东西,这里不进行证明,直接将结论拉过来用:
- 这样的区间只有 O ( n ) O(\sqrt{n}) O(n) 个。
- 对于一个区间 [ l , r ] [l,r] [l,r],满足 r = ⌊ k ⌊ k i ⌋ ⌋ r=\Big\lfloor\dfrac{k}{\lfloor\frac{k}{i}\rfloor}\Big\rfloor r=⌊⌊ik⌋k⌋。
因此我们可以考虑从 l = 1 l=1 l=1 开始,根据上述结论 2 算出 r r r,而 [ l , r ] [l,r] [l,r] 的值显然是 ( r − l + 1 ) × ⌊ k l ⌋ (r-l+1) \times \Big\lfloor\dfrac{k}{l}\Big\rfloor (r−l+1)×⌊lk⌋,然后 l ← r + 1 l\leftarrow r+1 l←r+1,又根据结论 1,复杂度为 O ( n ) O(\sqrt{n}) O(n)。
解决这个问题之后,回到我们原本的问题:
∑ i = 1 n f ( i ) g ( ⌊ k i ⌋ ) \sum_{i=1}^{n}f(i)g(\Big\lfloor\dfrac{k}{i}\Big\rfloor) i=1∑nf(i)g(⌊ik⌋)
同样的,我们考虑处理出所有 [ l , r ] [l,r] [l,r],满足 ∀ i , j ∈ [ l , r ] , ⌊ k i ⌋ = ⌊ k j ⌋ \forall i,j \in [l,r],\Big\lfloor\dfrac{k}{i}\Big\rfloor=\Big\lfloor\dfrac{k}{j}\Big\rfloor ∀i,j∈[l,r],⌊ik⌋=⌊jk⌋,下面定义 [ l , r ] [l,r] [l,r] 的值是 ∑ i = l r f ( i ) g ( ⌊ k i ⌋ ) \sum_{i=l}^{r}f(i)g(\Big\lfloor\dfrac{k}{i}\Big\rfloor) ∑i=lrf(i)g(⌊ik⌋)。
这里先将这些区间存下来,记作 [ l i , r i ] [l_i,r_i] [li,ri],然后我们注意到此时的 ⌊ k i ⌋ \Big\lfloor\dfrac{k}{i}\Big\rfloor ⌊ik⌋ 是一个常数,可以直接将 g ( ⌊ k i ⌋ ) g(\Big\lfloor\dfrac{k}{i}\Big\rfloor) g(⌊ik⌋) 提出去,此时 [ l i , r i ] [l_i,r_i] [li,ri] 对答案的贡献就变成了这样:
g ( ⌊ k l i ⌋ ) ∑ i = l i r i f ( i ) g(\Big\lfloor\dfrac{k}{l_i}\Big\rfloor)\sum_{i=l_i}^{r_i}f(i) g(⌊lik⌋)i=li∑rif(i)
然后因为可以快速计算 f ( r ) − f ( l ) f(r)-f(l) f(r)−f(l),所以照样可以 O ( n ) O(\sqrt{n}) O(n) 解决。
代码里面注意两个点:
- 大于 n n n 的区间右端点要变成 n n n。
- 当 ⌊ k l ⌋ \Big\lfloor\dfrac{k}{l}\Big\rfloor ⌊lk⌋ 为 0 的时候 r = n r=n r=n。
通用 Code:
n = Read(), k = Read(); ans = 0;
for (int l = 1, r; l <= n; l = r + 1)
{
if (k / l == 0) r = n;
else r = Min(k / (k / l), n);
++cnt; Left[cnt] = l, Right[cnt] = r;
}
for (int i = 1; i <= cnt; ++i)
ans += (f(Left[i], Right[i]) * g(k / Left[i]));
其中 f ( l , r ) f(l,r) f(l,r) 计算 ∑ i = l r f i \sum_{i=l}^{r}f_i ∑i=lrfi, g ( i ) g(i) g(i) 就是算 g ( i ) g(i) g(i)。
接下来看道例题:P2261 [CQOI2007]余数求和。
这道题给出 n , k n,k n,k,要求:
∑ i = 1 n k m o d i \sum_{i=1}^{n}k \bmod i i=1∑nkmodi
首先将 m o d \bmod mod 拆掉,将式子变成这样:
∑ i = 1 n ( k − i × ⌊ k i ⌋ ) \sum_{i=1}^{n}(k-i \times \Big\lfloor\dfrac{k}{i}\Big\rfloor) i=1∑n(k−i×⌊ik⌋)
然后发现这个 k k k 可以提出去,变成这样:
n k − ∑ i = 1 n ( i × ⌊ k i ⌋ ) nk-\sum_{i=1}^{n}(i \times \Big\lfloor\dfrac{k}{i}\Big\rfloor) nk−i=1∑n(i×⌊ik⌋)
我们只需要处理后面这个式子,对应到整除分块上,发现 f ( i ) = i , g ( ⌊ k i ⌋ ) = ⌊ k i ⌋ f(i)=i,g(\Big\lfloor\dfrac{k}{i}\Big\rfloor)=\Big\lfloor\dfrac{k}{i}\Big\rfloor f(i)=i,g(⌊ik⌋)=⌊ik⌋,满足 O ( 1 ) O(1) O(1) 计算 f ( r ) − f ( l ) f(r)-f(l) f(r)−f(l) 的条件,因此可以整除分块求后面这个式子,做完了。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P2261 [CQOI2007]余数求和
Date:2022/2/18
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 1e5 + 5;
int n, k, Left[MAXN], Right[MAXN], cnt;
LL ans = 0;
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }
int main()
{
n = Read(), k = Read(); ans = 1ll * n * k;
for (int l = 1, r; l <= n; l = r + 1)
{
if (k / l == 0) r = n;
else r = Min(k / (k / l), n);
++cnt; Left[cnt] = l, Right[cnt] = r;
}
for (int i = 1; i <= cnt; ++i)
ans = ans - 1ll * (k / Left[i]) * 1ll * (Left[i] + Right[i]) * (Right[i] - Left[i] + 1) / 2;
// 这里通过直接推式子代替了 f 和 g 函数
printf("%lld\n", ans); return 0;
}
3. 总结
整除分块将形如 ∑ i = 1 n f ( i ) g ( ⌊ k i ⌋ ) \sum_{i=1}^{n}f(i)g(\Big\lfloor\dfrac{k}{i}\Big\rfloor) ∑i=1nf(i)g(⌊ik⌋) 的式子化为了多个形如 g ( ⌊ k l i ⌋ ) ∑ i = l i r i f ( i ) g(\Big\lfloor\dfrac{k}{l_i}\Big\rfloor)\sum_{i=l_i}^{r_i}f(i) g(⌊lik⌋)∑i=lirif(i) 的式子,其中 f ( r ) − f ( l ) f(r)-f(l) f(r)−f(l) 能够快速计算,从而减少时间复杂度,降至 O ( n ) O(\sqrt{n}) O(n)。