「笔记」整除(数论)分块
写在前面
upd on 2023.1.26:修改排版,调整顺序,新增上取整类型的整除分块,新增一道例题。
推一下自己的莫比乌斯反演:Link。
引理
表示集合 的元素个数。上式即表示 的取值不多于 种。
证明
显然 即 中 的倍数的个数。
当 ,最极限的情况下 均两两不同,则最多有 种取值。当 ,此时有 ,最极限的情况下 均两两不同,仍是最多有 种取值。
综上,在 均两两不同的极限情况下, 的取值不多于 种。
数论分块
由引理,对于一类含有 的求和式 ( 为常数),由于 单调不增,故存在至多 个区间 ,使得区间内有: 成立。
数论分块是用来快速求得上述每个区间左右端点的一个数学小结论:对于任意一个 ,最大的满足 的 满足:
证明法 1
对于满足的 ,有:
则有:
即有:
又 ,则 最大值为
证明法 2
即
复杂度分析
由引理, 的取值不多于 种。则在数论分块中枚举的区间个数至多为 个。
扩展
对于上取整的情况 ,也有类似于上述形式的结论成立。对于任意一个 ,最小的满足 的 满足:
证明过程与证明法 1 类似。对于满足的 ,考虑下述不等式即可:
例 1
[AHOI2005]约数研究
令 为 的约数个数,求:。
1S,128MB。
对于 , 在 中其倍数个数为, 则中共有 个以其为约数的数。则有:
此时直接暴力即可通过本题。
若 ,考虑数论分块:
由上可知,对于每一个 ,存在区间 ,使得,区间 贡献即为 。
枚举这样的 计算贡献即可,复杂度 级别。
复制复制//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]余数求和
给定 ,求:
1S,128MB。
进行简单的转化:
同例一,存在多段 相等的区间 ,其贡献为 。通过数论分块和等差数列求和即可得到答案,复杂度 级别。
//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
约数和
定义,给定 ,求:。
1S,128MB。
将答案变为前缀和形式:
简单转化一下:
变换求和顺序,上式即为:
等价于枚举 中约数为 的数,
由例一,上式即为
同例二,通过数论分块和等差数列求和即可得到答案,复杂度 级别。
//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
这里有一张 的完全图,点有点权,边有边权。第 个节点的点权值为 ,边 的边权值为 。
组数据,每组数据给定整数 ,求由节点 构成的完全子图中边权的种类数。
,,。
2S,256MB。
问题实质是求 中的数两两 的种类数。考虑枚举 :
对于 时,显然当 时, 对答案有贡献,则有贡献的 满足的条件为:。
对于 ,考虑 中 的倍数的位置。显然其中最小的 的倍数为 ,最大的倍数为 。当满足 时 对答案有贡献。由整除分块可知 和 分别只有 和 种取值,但仅有 过大,我们考虑通过整除分块枚举所有 相等的区间 。
对于 ,也有 单调递减成立,则可以考虑在区间 上二分得到最大的满足 的 ,区间 对答案有贡献的数的取值范围即 。 地枚举所有区间后再 地累计贡献即可,总复杂度 级别,可以通过本题。
或者更进一步地,对于 ,满足上述条件实质上等价于 。枚举区间后 的值固定,则最大的 。注意这样计算出的 可能小于 ,需要特判一下。此时总复杂度变为 级别。
//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 真好玩,快去买。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!