火龙信奥
  • 首页
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

数论 - 整除分块

作者: 作者的头像   huolong , 时间:2022-09-23 09:22:52 , 所有人可见, 阅读  13

引入

整除分块是数论问题中非常常用的技巧,先来看一个简单的问题:

已知,给定一个 $n$,求 $ f(n) = \sum_{i=1}^n \left \lfloor \frac{n}{i} \right \rfloor $ 的值。

先假设 $1 \le n \le 10^6 $,第一反应肯定是 $O(n)$ 遍历一遍 $1 \sim n$ 直接求和,轻松得到答案。

int ans = 0;
for(int i=1; i<=n; i++){
    ans += n/i;
}

但如果 $1 \le n \le 10^{9} $ ,甚至 $10^{12}$ 呢,显然就不能 $O(n)$ 暴力了,这就得用到接下来要介绍的整除分块的内容。

找规律找规律

我们先试着计算一下 $\left \lfloor \frac{n}{i} \right \rfloor$ 前几项(几十项)的值,比如 $n = 16$:

| $i$ | 1 | 2 | 3 | 4 | 5 | 6 | 7 |8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16
| ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | | $\left \lfloor \frac{n}{i} \right \rfloor$ |16 | 8 | 5 | 4 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1

可以发现 $\left \lfloor \frac{n}{i} \right \rfloor$ 的取值在连续的一段区间内是相同的,那么就启发我们是否可以将其分为若干块分别进行计算呢,这就是整除分块的核心思想了。

分块数量(时间复杂度分析)

根据数学知识可以知道 $\left \lfloor \frac{n}{i} \right \rfloor$ 的值的集合大小最多为 $2\sqrt{n}$,我们可以简单证明一下这个数学现象:

对于正整数 $i$,当 $i \le \sqrt{n}$ 时,由于 $i$ 最多有 $\sqrt{n}$ 种取值,$\left \lfloor \frac{n}{i} \right \rfloor$ 最多也只能有 $\sqrt{n}$ 种取值;

当 $i \gt \sqrt{n}$ 时,由于 $i \gt n$ 之后,$\left \lfloor \frac{n}{i} \right \rfloor$ 的值始终为 $0$,而在 $\sqrt{n} \lt i \le n$ 时 $i$ 最多也只有 $\sqrt{n}$ 种取值,$\left \lfloor \frac{n}{i} \right \rfloor$ 最多也只能有 $\sqrt{n}$ 种取值。

综上所述,$\left \lfloor \frac{n}{i} \right \rfloor$ 的值的集合大小最多为 $2\sqrt{n}$ ,这样整除分块的时间复杂度就为 $O(\sqrt{n})$。

分块边界

先让 $l$ 为分块的左边界,那么这块的单个值 $k = \left \lfloor \frac{n}{l} \right \rfloor$ ,这样右边界 $r$ 就是值为 $k$ 的最大下标 $i$,也就是说要找满足 $i \le \frac{n}{k}$ 的最大的 $i$,即 $r = max(i) = \frac{n}{k}$ ,将 $k$ 代入,得到 $r = \left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor $ 。

这样每块的左右边界都可以用一个确定的式子计算得到,这样分块的值就为单值 × 区间长度,即 $k * (r-l+1)$。

模板

ll division_block(ll n){
    ll res = 0;
    for(ll l = 1, r; l <= n; l = r + 1){
        r = n / (n / l);
        res += n / l * (r - l + 1);
    }
    return res;
}

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

©2026加盟我们 | 关于我们 | ACM课程 | 常见问题 | 成果墙 | 评测记录 | 浙ICP备2021013995号
在线画图 | OI WIki | 打字练习
火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码