数论分块学习笔记

本文介绍了一种高效的数论分块算法,用于快速计算特定数学表达式。通过改变枚举方式并利用预处理技巧,该算法能在O(√n)的时间复杂度内解决问题。文章还提供了两个实例问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题引入

试快速计算下式:
Σi=1nf(i)g(⌊ni⌋) \Sigma_{i=1}^{n}f(i)g(\lfloor \frac{n}{i} \rfloor)Σi=1nf(i)g(⌊in⌋)

其中,f(x)f(x)f(x) 的前缀和已预处理(或可预处理),g(x)g(x)g(x) 可以 O(1)O(1)O(1) 计算。

二、分析

首先,我们可以发现,在 iii111nnn 单调递增的过程中, ⌊ni⌋\lfloor \frac{n}{i} \rfloorin 的值从 nnn111 单调递减,但变化次数远远少于 nnn 次,因此可以考虑将 ⌊ni⌋\lfloor \frac{n}{i} \rfloorin 相同的情况合并,统一计算。更换枚举方式,简化为下式:
Σi=1nf(i)g(⌊ni⌋)=Σj∈S(sumf(rj)−sumf(lj))g(j),S={⌊ni⌋∣i∈N+,i⩽n} \Sigma_{i = 1}^{n}f(i)g(\lfloor \frac{n}{i}\rfloor) = \Sigma_{j \in S} (sumf(r_{j}) - sumf(l_{j}))g(j), S = \lbrace\lfloor\frac{n}{i}\rfloor\mid i \in \mathbb{N}_{+}, i \leqslant n \rbraceΣi=1nf(i)g(⌊in⌋)=ΣjS(sumf(rj)sumf(lj))g(j),S={⌊iniN+,in}

经过简化,从 111nnn 的枚举范围降低到了枚举 SSS 集合。那么 SSS 集合究竟有多大呢?有引理如下。

Lemma. ∀n∈N+,∣{⌊ni⌋∣i∈N+,i⩽n}∣⩽2n,\forall n \in \mathbb{N}_{+}, \left|\left\{\lfloor\frac{n}{i}\rfloor \mid i \in \mathbb{N}_{+},i\leqslant n\right\}\right| \leqslant 2\sqrt{n},nN+,{iniN+,in}2n∣S∣\left|S\right|S 表示集合 SSS 的大小。

简略证明如下:
iii 小于 n\sqrt{n}n 时,⌊ni⌋\lfloor\frac{n}{i}\rfloorin 至多有 n\sqrt{n}n 种取值。
iii 大于 n\sqrt{n}n 时,1⩽⌊ni⌋⩽n1\leqslant \lfloor\frac{n}{i}\rfloor \leqslant \sqrt n1inn ,也至多有 n\sqrt{n}n 种取值。
因此,⌊ni⌋\lfloor\frac{n}{i}\rfloorin 总共至多 2n2\sqrt{n}2n 种取值。

最后,分析一下更换枚举后的复杂度。枚举至多 2n2\sqrt{n}2n 次,单次计算区间和和 g(x)g(x)g(x) 函数值均可以在 O(1)O(1)O(1) 的时间复杂度内解决,因此总复杂度为 O(n)O(\sqrt{n})O(n)

三、例题和代码

(1)UVA11526

题目大意:TTT 组数据,每组给定一个 nnn ,求下式的值。
Σi=1n⌊ni⌋\Sigma_{i=1}^{n}\lfloor\frac{n}{i}\rfloorΣi=1nin

解:数论分块板子题。其中 f(x)≡1,g(x)=xf(x) \equiv 1,g(x) = xf(x)1,g(x)=x。代码如下。

#include<bits/stdc++.h>
using namespace std;
int T, n;
int sumf(int x){
	return x;
}
int g(int x){
	return x;
}
int main(){
	scanf("%d", &T);
	while (T --){
		scanf("%d", &n);
		long long l = 1, r, ans = 0;
		while (l <= n){
			r = n / (n / l);
			ans += (sumf(r) - sumf(l - 1)) * g(n / l);
			l = r + 1;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

(2) P2261

题目大意:给定 nnnkkk ,求下式的值。
G(n,k)=Σi=1nk%iG(n,k) = \Sigma_{i = 1}^{n}{k}\%{i}G(n,k)=Σi=1nk%i

其中 %\%% 为取余运算。
解:转化。
G(n,k)=Σi=1nk%i=Σi=1n(k−i⌊ki⌋)=Σi=1nk−Σi=1ni⌊ki⌋G(n,k) = \Sigma_{i = 1}^{n}{k}\%{i} = \Sigma_{i = 1}^{n}(k - i\lfloor\frac{k}{i}\rfloor) = \Sigma_{i = 1}^{n}k - \Sigma_{i = 1}^{n}i\lfloor\frac{k}{i}\rfloorG(n,k)=Σi=1nk%i=Σi=1n(kiik⌋)=Σi=1nkΣi=1niik
与数论分块的模板式子很相似,可以确定两个函数分别为 f(x)=x,g(x)=xf(x) = x, g(x) = xf(x)=x,g(x)=x 。不过上界不同,分类讨论即可。
对于 k⩽nk\leqslant nkn的情况,由于 i⩾ki\geqslant kik⌊ki⌋=0\lfloor\frac{k}{i}\rfloor = 0ik=0,没有影响。
对于 k>nk \gt nk>n 的情况,需要判断区间的右端点 rrr 是否超过 nnn ,如果超过,则需要修改。代码如下。

#include<bits/stdc++.h>
using namespace std;
int n, k;
long long sumf(int x){
	return 1ll * x * (x + 1) / 2;
}
long long g(int x){
	return x;
}
int main(){
	scanf("%d%d", &n, &k);
	if (k <= n){
		long long l = 1, r, ans = 0;
		while (l <= k){
			r = k / (k / l);
			ans += (sumf(r) - sumf(l - 1)) * g(k / l);
			l = r + 1;
		}
		printf("%lld\n", 1ll * n * k - ans);
	}
	else {
		long long l = 1, r, ans = 0;
		while (l <= n){
			r = k / (k / l);
			if (r > n) r = n;
			ans += (sumf(r) - sumf(l - 1)) * g(k / l);
			l = r + 1;
		}
		printf("%lld\n", 1ll * n * k - ans);
	}
	return 0;
}
### Python 实现数论分块 数论分块是一种优化技巧,在处理某些涉及整除问题的区间查询时非常有效。其核心思想在于利用整除性质减少重复计算次数。 对于给定范围 `[l, r]` 和常量 `d`,当遍历该范围内每一个元素并执行模运算或整除操作时,可能会遇到大量冗余计算。数论分块通过预先计算若干个连续区间的贡献来加速这一过程。 #### 数论分块原理说明 假设要解决形如 $\sum_{i=l}^{r}\lfloor\frac{n}{i}\rfloor$ 的求和问题: - 对于任意正整数 $n$ 及其因子 $k=\lfloor \frac{n}{m} \rfloor$ - 存在一个最大值 $j=min(n/(n//i),r)$ 使得对于所有的 $t∈[i,j]$ 都满足 $(n/t)=(n/i)$ - 这意味着我们可以一次性处理整个区间内具有相同商的结果,而不是逐项累加 #### 示例代码实现 下面是一个简单的 Python 函数用来展示如何应用数论分块方法来进行上述类型的求和: ```python def num_theory_block_sum(n): i = 1 total = 0 while i <= n: block_end = min(n // (n // i), n) # 计算当前块结束位置 value = n // i # 当前块内的公共值 count = block_end - i + 1 # 块大小 total += value * count # 更新总和 i = block_end + 1 # 移动到下一个未处理的位置 return total if __name__ == "__main__": test_value = int(input("Enter an integer to calculate sum of floor divisions up to this number: ")) result = num_theory_block_sum(test_value) print(f"The calculated sum is {result}.") ``` 此函数接收一个参数 `n` 并返回从 1 到 n 所有整数与其对应的最大不超过自身的倍数值之间的向下取整后的商之和[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值