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

写在前面

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

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

引理

nN,|{nddN}|2n

|V| 表示集合 V 的元素个数。上式即表示 nd 的取值不多于 2n 种。

证明

显然 nd1nd 的倍数的个数。

dn,最极限的情况下 nd 均两两不同,则最多有 n 种取值。当 dn,此时有 ndn,最极限的情况下 nd 均两两不同,仍是最多有 n 种取值。

综上,在 nd 均两两不同的极限情况下, nd 的取值不多于 2n 种。

数论分块

由引理,对于一类含有 ni 的求和式 (n 为常数),由于 ni 单调不增,故存在至多 2n 个区间 [l,r],使得区间内有: ni=nj(i,j[l,r]) 成立。

数论分块是用来快速求得上述每个区间左右端点的一个数学小结论:对于任意一个 i,最大的满足 ni=njj 满足:

j=nni

证明法 1

对于满足ni=njj(ij),有:

{ninj=nj+r(0r<1)nj+1<ni

则有:

(ninj)(nnjnni)(jnni)

(nj+1<ni)(nni<nnj+1)(nni<j+1)

即有:

jnni<j+1

jN,则 j最大值为nni

证明法 2

nininninni=i=iinni

j=nni

复杂度分析

由引理,nd 的取值不多于 2n 种。则在数论分块中枚举的区间个数至多为 2n 个。

扩展

对于上取整的情况 ni,也有类似于上述形式的结论成立。对于任意一个 j,最的满足 ni=nji 满足:

i=nnj

证明过程与证明法 1 类似。对于满足ni=njj(ij),考虑下述不等式即可:

{njni=njr(0r<1)ni1>nj

例 1

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

i=1nf(i)

n106
1S,128MB。

对于 i, 在1n 中其倍数个数为ni, 则1n中共有ni 个以其为约数的数。则有:i=1nf(i)=i=1nni

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


n1014,考虑数论分块:
由上可知,对于每一个 l[1,n],存在区间 [l,r],r=nnl,使得ni=nj(i,j[l,r]),区间 [l,r] 贡献即为 (rl+1)×nl

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

复制复制
//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,求:

i=1nkmodi

n,k109
1S,128MB。

进行简单的转化:

i=1nkmodi=i=1nkki×i=n×ki=1nki×i

同例一,存在多段 ki相等的区间 [l,r],其贡献为 ki×i=lri。通过数论分块和等差数列求和即可得到答案,复杂度 O(2n) 级别。

//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)=ini,给定 x,y,求:

i=xyf(i)

x,y2×109
1S,128MB。

将答案变为前缀和形式:

i=xyf(i)=i=1yf(i)i=1x1f(i)

简单转化一下:

i=1nf(i)=i=1nkik

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

k=1nkik(in)

kik 等价于枚举 1n 中约数为 k 的数,
由例一,上式即为 k=1nk×nk
同例二,通过数论分块和等差数列求和即可得到答案,复杂度 O(2n) 级别。

//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=1018 的完全图,点有点权,边有边权。第 i 个节点的点权值为 i,边 (u,v) 的边权值为 gcd(u,v)
t 组数据,每组数据给定整数 l,r,求由节点 lr 构成的完全子图中边权的种类数。
1t1001lr1018l109
2S,256MB。

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

对于 dl 时,显然当 2×dr 时,d 对答案有贡献,则有贡献的 d 满足的条件为:d[l,r2]

对于 d<l,考虑 [l,r]d 的倍数的位置。显然其中最小的 d 的倍数为 ld×d,最大的倍数为 rd×d。当满足 ld<rdd 对答案有贡献。由整除分块可知 ldrd 分别只有 lr 种取值,但仅有 r 过大,我们考虑通过整除分块枚举所有 ld 相等的区间 [i,j]

对于 d[i,j],也有 rd 单调递减成立,则可以考虑在区间 [i,j] 上二分得到最大的满足 ld<rdd,区间 [i,j] 对答案有贡献的数的取值范围即 [i,d]O(l) 地枚举所有区间后再 O(logl) 地累计贡献即可,总复杂度 O(llogl) 级别,可以通过本题。

或者更进一步地,对于 d[i,j],满足上述条件实质上等价于 (ld+1)×dr。枚举区间后 ld 的值固定,则最大的 d=min(r(ld+1),j)。注意这样计算出的 d 可能小于 i,需要特判一下。此时总复杂度变为 O(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 @   Luckyblock  阅读(2199)  评论(4编辑  收藏  举报
编辑推荐:
· 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框架的用法!
点击右上角即可分享
微信分享提示