「笔记」整除(数论)分块

写在前面

upd on 2023.1.26:修改排版,调整顺序,新增上取整类型的整除分块,新增一道例题。

推一下自己的莫比乌斯反演:Link

引理

\[\forall n\in N,\left| \left\{ \left\lfloor\frac{n}{d}\right\rfloor \mid d\in \N\right\} \right| \le \left\lfloor{2\sqrt{n}}\right\rfloor \]

\(|V|\) 表示集合 \(V\) 的元素个数。上式即表示 \(\left\lfloor\frac{n}{d}\right\rfloor\) 的取值不多于 \(\left\lfloor{2\sqrt{n}}\right\rfloor\) 种。

证明

显然 \(\left\lfloor\frac{n}{d}\right\rfloor\)\(1\sim n\)\(d\) 的倍数的个数。

\(d\le \left\lfloor\sqrt{n}\right\rfloor\),最极限的情况下 \(\left\lfloor\frac{n}{d}\right\rfloor\) 均两两不同,则最多有 \(\left\lfloor\sqrt{n}\right\rfloor\) 种取值。当 \(d\ge \left\lfloor\sqrt{n}\right\rfloor\),此时有 \(\left\lfloor\frac{n}{d}\right\rfloor \le \left\lfloor\sqrt{n}\right\rfloor\),最极限的情况下 \(\left\lfloor\frac{n}{d}\right\rfloor\) 均两两不同,仍是最多有 \(\left\lfloor\sqrt{n}\right\rfloor\) 种取值。

综上,在 \(\left\lfloor\frac{n}{d}\right\rfloor\) 均两两不同的极限情况下, \(\left\lfloor\frac{n}{d}\right\rfloor\) 的取值不多于 \(\left\lfloor{2\sqrt{n}}\right\rfloor\) 种。

数论分块

由引理,对于一类含有 \(\left\lfloor\frac{n}{i}\right\rfloor\) 的求和式 (\(n\) 为常数),由于 \(\left\lfloor\frac{n}{i}\right\rfloor\) 单调不增,故存在至多 \(2\left\lfloor \sqrt{n} \right\rfloor\) 个区间 \([l,r]\),使得区间内有: \(\left\lfloor\frac{n}{i}\right\rfloor = \left\lfloor\frac{n}{j}\right\rfloor(i,j\in [l,r])\) 成立。

数论分块是用来快速求得上述每个区间左右端点的一个数学小结论:对于任意一个 \(i\),最大的满足 \(\left\lfloor\frac{n}{i}\right\rfloor = \left\lfloor\frac{n}{j}\right\rfloor\)\(j\) 满足:

\[j=\left\lfloor{\dfrac{n}{\left\lfloor{\frac{n}{i}}\right\rfloor}}\right\rfloor \]

证明法 1

对于满足\(\left\lfloor\frac{n}{i}\right\rfloor = \left\lfloor\frac{n}{j}\right\rfloor\)\(j(i\le j)\),有:

\[\begin{cases} \left\lfloor{\dfrac{n}{i}}\right\rfloor\le \dfrac{n}{j} = \left\lfloor\dfrac{n}{j}\right\rfloor + r (0\le r < 1)\\ \dfrac{n}{j+1}< \left\lfloor{\dfrac{n}{i}}\right\rfloor \end{cases}\]

则有:

\[\left(\left\lfloor{\dfrac{n}{i}}\right\rfloor\le \dfrac{n}{j}\right)\Longrightarrow \left(\dfrac{n}{\dfrac{n}{j}}\le \dfrac{n}{\left\lfloor{\dfrac{n}{i}}\right\rfloor}\right) \Longrightarrow \left(j \le \dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor}\right)\]

\[\left(\dfrac{n}{j+1}< \left\lfloor{\dfrac{n}{i}}\right\rfloor\right) \Longrightarrow \left(\dfrac{n}{\left\lfloor{\dfrac{n}{i}}\right\rfloor} < \dfrac{n}{\dfrac{n}{j+1}}\right) \Longrightarrow \left(\dfrac{n}{\left\lfloor{\dfrac{n}{i}}\right\rfloor} < j + 1\right)\]

即有:

\[j \le \dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor} < j+1 \]

\(j \in \mathbb{N}\),则 \(j\)最大值为\(\left\lfloor{\dfrac{n}{\left\lfloor\frac{n}{i}\right\rfloor}}\right\rfloor\)

证明法 2

\[\begin{aligned} &\left\lfloor\dfrac{n}{i}\right\rfloor \le \dfrac{n}{i}\\ \Longrightarrow &\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor}\right\rfloor \ge \left\lfloor\dfrac{n}{\frac{n}{i}}\right\rfloor = \lfloor i\rfloor =i\\ \Longrightarrow &i \le \left\lfloor\dfrac{n}{\left\lfloor\frac{n}{i}\right\rfloor}\right\rfloor \end{aligned}\]

\(j = \left\lfloor\dfrac{n}{\left\lfloor\frac{n}{i}\right\rfloor}\right\rfloor\)

复杂度分析

由引理,\(\left\lfloor\frac{n}{d}\right\rfloor\) 的取值不多于 \(\left\lfloor{2\sqrt{n}}\right\rfloor\) 种。则在数论分块中枚举的区间个数至多为 \(\left\lfloor{2\sqrt{n}}\right\rfloor\) 个。

扩展

对于上取整的情况 \(\left\lceil\frac{n}{i}\right\rceil\),也有类似于上述形式的结论成立。对于任意一个 \(j\),最的满足 \(\left\lceil\frac{n}{i}\right\rceil = \left\lceil\frac{n}{j}\right\rceil\)\(i\) 满足:

\[i = \left\lceil{\dfrac{n}{\left\lceil{\frac{n}{j}}\right\rceil}}\right\rceil \]

证明过程与证明法 1 类似。对于满足\(\left\lceil\frac{n}{i}\right\rceil = \left\lceil\frac{n}{j}\right\rceil\)\(j(i\le j)\),考虑下述不等式即可:

\[\begin{cases} \left\lceil{\dfrac{n}{j}}\right\rceil\ge \dfrac{n}{i} = \left\lceil\dfrac{n}{j}\right\rceil - r (0\le r < 1)\\ \dfrac{n}{i-1}> \left\lceil{\dfrac{n}{j}}\right\rceil \end{cases}\]

例 1

[AHOI2005]约数研究
\(f(i)\)\(i\) 的约数个数,求:

\[\sum\limits_{i=1}^{n} f(i) \]

\(n \le 10^6\)
1S,128MB。

对于 \(i\), 在\(1\sim n\) 中其倍数个数为\(\left\lfloor\frac{n}{i}\right\rfloor\), 则\(1\sim n\)中共有\(\left\lfloor\frac{n}{i}\right\rfloor\) 个以其为约数的数。则有:\(\sum\limits_{i=1}^{n} f(i) = \sum\limits_{i=1}^{n} \left\lfloor\frac{n}{i}\right\rfloor\)

此时直接\(O(n)\)暴力即可通过本题。


\(n\le 10^{14}\),考虑数论分块:
由上可知,对于每一个 \(l\in [1,n]\),存在区间 \([l, r], r = \left\lfloor\dfrac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor\),使得\(\left\lfloor\frac{n}{i}\right\rfloor = \left\lfloor\frac{n}{j}\right\rfloor(i,j\in [l,r])\),区间 \([l,r]\) 贡献即为 \((r-l+1)\times \left\lfloor\dfrac{n}{l}\right\rfloor\)

枚举这样的 \(j\) 计算贡献即可,复杂度 \(O(2\sqrt{n})\) 级别。

//By:Luckyblock
#include <cstdio>
#include <cctype>
#define ll long long
//=============================================================
int N, ans;
//=============================================================
inline int read()
{
    int f = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
    return f * w;
}
//=============================================================
int main()
{
    N = read();
    for(int i = 1, j; i <= N; i = j + 1)
    {
      j = N / (N / i);
      ans += (j - i + 1) * (N / i);
    }
    printf("%d\n", ans);
    return 0;
}

例 2

[CQOI2007]余数求和
给定 \(n, k\),求:

\[\sum\limits_{i=1}^{n} {k\!\!\!\mod\! i} \]

\(n,k \le 10^9\)
1S,128MB。

进行简单的转化:

\[\begin{aligned} &\sum\limits_{i=1}^{n} {k\!\!\!\mod\! i} \\ = &\sum\limits_{i=1}^{n} k-\left\lfloor\dfrac{k}{i}\right\rfloor \times i\\ = &n \times k - \sum_{i=1}^{n} \left\lfloor\dfrac{k}{i}\right\rfloor \times i \end{aligned}\]

同例一,存在多段 \(\left\lfloor\frac{k}{i}\right\rfloor\)相等的区间 \([l,r]\),其贡献为 \(-\left\lfloor\frac{k}{i}\right\rfloor \times \sum\limits_{i=l}^{r} i\)。通过数论分块和等差数列求和即可得到答案,复杂度 \(O(2\sqrt{n})\) 级别。

//By:Luckyblock
#include <cstdio>
#include <cctype>
#define ll long long
//=============================================================
ll N, K, Ans;
//=============================================================
inline ll read()
{
    ll f = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
    return f * w;
}
ll GetSum(ll L, ll R) {return (R - L + 1ll) * (L + R) / 2ll;} //等差数列求和
//=============================================================
int main()
{
    N = read(), K = read(), Ans = N * K;
    for(ll i = 1, j; i <= N; i = j + 1ll)
    {
      if(K / i == 0) break; //当k/i = 0时,对答案无贡献
      j = K / (K / i); j = j > N ? N : j;
      Ans -= (K / i) * GetSum(i, j);
    }
    printf("%lld", Ans);
    return 0;
}

例 3

约数和
定义\(f(n) = \sum\limits_{i\mid n} i\),给定 \(x, y\),求:

\[\sum\limits_{i=x}^{y}f(i) \]

\(x,y \le 2\times 10^9\)
1S,128MB。

将答案变为前缀和形式:

\[\sum\limits_{i=x}^{y}f(i) = \sum\limits_{i=1}^{y}f(i) - \sum\limits_{i=1}^{x - 1}f(i) \]

简单转化一下:

\[\sum\limits_{i=1}^{n} f(i) = \sum\limits_{i=1}^{n} \sum\limits_{k\mid i} k \]

变换求和顺序,上式即为:

\[\sum\limits_{k=1}^{n} \sum\limits_{k\mid i} k (i\le n) \]

\(\sum\limits_{k\mid i}k\) 等价于枚举 \(1\sim n\) 中约数为 \(k\) 的数,
由例一,上式即为 \(\sum\limits_{k=1}^{n} k\times \left\lfloor\dfrac{n}{k}\right\rfloor\)
同例二,通过数论分块和等差数列求和即可得到答案,复杂度 \(O(2\sqrt{n})\) 级别。

//By:Luckyblock
#include <cstdio>
#include <cctype>
#include <algorithm>
#define ll long long
//=============================================================
ll x, y;
//=============================================================
inline ll read()
{
    ll f = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
    return f * w;
}
ll GetSum(ll L, ll R) {return (L + R) * (R - L + 1) / 2;}
ll GetAns(ll N)
{
    ll ret = 0;
    for(ll l = 1, r; l <= N; l = r + 1)
    {
      r = N / (N / l);
      ret += GetSum(l, r) * (N / l);
    }
    return ret;
}
//=============================================================
int main()
{
    x = read(), y = read();
    printf("%lld", GetAns(y) - GetAns(x - 1));
    return 0;
}

例 4

CF1780E
这里有一张 \(n=10^{18}\) 的完全图,点有点权,边有边权。第 \(i\) 个节点的点权值为 \(i\),边 \((u,v)\) 的边权值为 \(\gcd(u,v)\)
\(t\) 组数据,每组数据给定整数 \(l,r\),求由节点 \(l\sim r\) 构成的完全子图中边权的种类数。
\(1\le t\le 100\)\(1\le l\le r\le 10^{18}\)\(l\le 10^9\)
2S,256MB。

问题实质是求 \([l, r]\) 中的数两两 \(\gcd\) 的种类数。考虑枚举 \(d = \gcd(i, j) (l\le i<j\le r)\)

对于 \(d\ge l\) 时,显然当 \(2\times d \le r\) 时,\(d\) 对答案有贡献,则有贡献的 \(d\) 满足的条件为:\(d\in \left[l, \left\lfloor\frac{r}{2}\right\rfloor\right]\)

对于 \(d< l\),考虑 \([l,r]\)\(d\) 的倍数的位置。显然其中最小的 \(d\) 的倍数为 \(\left\lceil \frac{l}{d} \right\rceil \times d\),最大的倍数为 \(\left\lfloor \frac{r}{d} \right\rfloor \times d\)。当满足 \(\left\lceil \frac{l}{d} \right\rceil < \left\lfloor \frac{r}{d} \right\rfloor\)\(d\) 对答案有贡献。由整除分块可知 \(\left\lceil \frac{l}{d} \right\rceil\)\(\left\lfloor \frac{r}{d} \right\rfloor\) 分别只有 \(\sqrt{l}\)\(\sqrt{r}\) 种取值,但仅有 \(r\) 过大,我们考虑通过整除分块枚举所有 \(\left\lceil \frac{l}{d} \right\rceil\) 相等的区间 \([i,j]\)

对于 \(d\in [i,j]\),也有 \(\left\lfloor \frac{r}{d} \right\rfloor\) 单调递减成立,则可以考虑在区间 \([i,j]\) 上二分得到最大的满足 \(\left\lceil \frac{l}{d} \right\rceil < \left\lfloor \frac{r}{d} \right\rfloor\)\(d\),区间 \([i,j]\) 对答案有贡献的数的取值范围即 \([i, d]\)\(O(\sqrt{l})\) 地枚举所有区间后再 \(O(\log l)\) 地累计贡献即可,总复杂度 \(O(\sqrt{l}\log l)\) 级别,可以通过本题。

或者更进一步地,对于 \(d\in [i,j]\),满足上述条件实质上等价于 \(\left(\left\lceil \frac{l}{d} \right\rceil + 1\right)\times d \le r\)。枚举区间后 \(\left\lceil \frac{l}{d} \right\rceil\) 的值固定,则最大的 \(d = \min\left(\frac{r}{\left(\left\lceil \frac{l}{d} \right\rceil + 1\right)}, j\right)\)。注意这样计算出的 \(d\) 可能小于 \(i\),需要特判一下。此时总复杂度变为 \(O(\sqrt{l})\) 级别。

//By:Luckyblock
/*
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>
#define LL long long
//=============================================================
//=============================================================
inline LL read() {
	LL f = 1, w = 0; char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
	for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
	return f * w;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    LL l = read(), r = read(), ans = 0, p = 0;
    if (r / 2 >= l) ans += r / 2 - l + 1;
    for (LL j = l - 1, i; j; j = i - 1) {
      i = ceil(1.0 * l / ceil(1.0 * l / j));
      LL k = ceil(1.0 * l / j);
      ans += std::max(0ll, std::min(r / (k + 1), j) - i + 1);
    }
    printf("%lld\n", ans);
  }
  return 0;
}

写在最后

参考资料:

数论分块属于小工具一类的知识点。推式子时要尽量将式子转化为类似形式上。

证明法 1 是自己 yy 的,有不当之处请不吝赐教。

无主之地 3 真好玩,快去买。

posted @ 2020-04-01 17:46  Luckyblock  阅读(2148)  评论(4编辑  收藏  举报