「ACM/OI」【数论】Update:1.18

数论分块

介绍

  • 数论分块可以快速计算一些含有除法向下取整的和式(即形如 ∑ i = 1 n f ( i ) g ( ⌊ n i ⌋ ) \sum_{i=1}^nf(i)g(\left\lfloor\dfrac n i\right\rfloor) i=1nf(i)g(in) 的和式)。当可以在 O ( 1 ) O(1) O(1) 内计算 f ( r ) − f ( l ) f(r)-f(l) f(r)f(l) 或已经预处理出 f f f 的前缀和时,数论分块就可以在 O ( n ) O(\sqrt n) O(n ) 的时间内计算上述和式的值。

引入

  • 思考如何求解 ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^n\left\lfloor\dfrac ni\right\rfloor i=1nin 1 ≤ n ≤ 1 0 12 1\leq n \leq 10^{12} 1n1012
  • 暴力 O ( n ) O(n) O(n) 枚举显然是不行的,但如果一时没有思路,不妨先尝试通过暴力发现一些什么:
  • 对于 n = 11 n=11 n=11 ⌊ n i ⌋ = { 11 , 5 , 3 , 2 , 2 , 1 , 1 , 1 , 1 , 1 , 1 } \left\lfloor\dfrac ni\right\rfloor = \{ 11,5,3,2,2,1,1,1,1,1,1 \} in={11,5,3,2,2,1,1,1,1,1,1}
  • 对于 n = 30 n=30 n=30 ⌊ n i ⌋ = { 30 , 15 , 10 , 7 , 6 , 5 , 4 , 3 , 3 , 3 , 2 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 } \left\lfloor\dfrac ni\right\rfloor = \{30,15,10,7,6,5,4,3,3,3,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 \} in={30,15,10,7,6,5,4,3,3,3,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
  • 显而易见,数列中有几段数值相同的连续部分,即对于同一个 n n n,有不少 i i i 使得 ⌊ n i ⌋ \left\lfloor\dfrac n i\right\rfloor in 的值相同,没有必要多次计算,于是整除分块便出现了。如何快速计算每一个数值对应的区间呢?
  • 对于枚举的每个区间的左端点 l l l,首先易得该区间的数值均为 ⌊ n l ⌋ \left\lfloor\dfrac n l\right\rfloor ln,由结论可知区间右端点 r = ⌊ n ⌊ n l ⌋ ⌋ r=\left\lfloor\dfrac n{\lfloor\frac n l\rfloor}\right\rfloor r=lnn

时间复杂度

  • O ( n ) O(\sqrt n) O(n ). 可以证明块数不大于 2 n 2\sqrt n 2n 。简单来说可以分类讨论一下,若 i ≤ n i \leq \sqrt n in i i i 最多可以有 n \sqrt n n 种取值,因此 ⌊ n i ⌋ \left\lfloor\dfrac n i\right\rfloor in 最多有 n \sqrt n n 种取值;若 i > n i > \sqrt n i>n ,显然 ⌊ n i ⌋ \left\lfloor\dfrac n i\right\rfloor in 最多有 n \sqrt n n 种取值。综上, ⌊ n i ⌋ \left\lfloor\dfrac n i\right\rfloor in 最多有 2 n 2\sqrt n 2n 种取值。整个算法时间复杂度为 O ( n ) O(\sqrt n) O(n )

例题1:约数个数

题目描述:
σ ( x ) σ(x) σ(x) 表示 x x x 的约数个数,给定 n n n ( 1 ≤ n < 2 32 ) (1\leq n < 2^{32}) (1n<232),求 ∑ i = 1 n σ ( i ) \sum_{i=1}^nσ(i) i=1nσ(i)

分析

  • 数学语言: ∑ i = 1 n σ ( i ) = ∑ i = 1 n s u m ( d ∣ i ) = ∑ d = 1 n ⌊ n d ⌋ \sum_{i=1}^n σ(i)=\sum_{i=1}^n sum(d|i)=\sum_{d=1}^n\left\lfloor\dfrac n d\right\rfloor i=1nσ(i)=i=1nsum(di)=d=1ndn
  • 也就是说,相较于朴素思想即统计 [ 1 , n ] [1,n] [1,n] 每个数的约数个数,我们转化为考虑每一个约数 d ∈ [ 1 , n ] d \in [1,n] d[1,n] 在区间中的总贡献(某个约数在所有数的约数集合中出现的总次数),对于总贡献相同的区间(某些约数的出现次数相同),答案累加 区间长度乘上出现次数 即可。

代码

void solve() {
	ll n; cin >> n;
	ll ans = 0;
	for(ll l=1,r;l<=n;l=r+1) { // 枚举区间左端点
		r = n / (n / l);       // 计算区间右端点
		ll h = n / l;          // 该区间约数的相同出现次数
		ans += h*(r-l+1);
	}
	cout << ans;
}

例题2:约数和

题目描述:
σ ( x ) σ(x) σ(x) 表示 x x x 所有约数的和,给定 x , y x,y x,y ( 1 ≤ x < y ≤ 2 × 1 0 9 ) (1\leq x < y \leq 2\times 10^9) (1x<y2×109),求 ∑ i = x y σ ( i ) \sum_{i=x}^yσ(i) i=xyσ(i)

分析

  • 数学语言: ∑ i = 1 n σ ( i ) = ∑ i = 1 n ∑ d ∣ x d = ∑ d = 1 n d × ⌊ n d ⌋ \sum_{i=1}^n σ(i)=\sum_{i=1}^n \sum_{d|x}d=\sum_{d=1}^n d\times \left\lfloor\dfrac n d\right\rfloor i=1nσ(i)=i=1ndxd=d=1nd×dn
  • 基本思想与例题1大同小异,注意记录的是约数的和,考虑 [ l , r ] [l,r] [l,r] 约数区间和。
  • 另外本题求解的是 [ x , y ] [x,y] [x,y] 中数的约数和,利用前缀和的思想分别求解 f y f_y fy f x − 1 f_{x-1} fx1,作差即为答案。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
ll solve(int n) {
	ll ans = 0;
	for(ll l=1,r;l<=n;l=r+1) {
		r = n / (n / l);
		ll h = n / l;
		ans += h*(l+r)*(r-l+1)/2; // 等差数列求和
	}
	return ans;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int x,y; cin >> x >> y;
    cout << solve(y)-solve(x-1) << "\n";
	return 0;
} 

例题3:余数求和

题目描述:
给定正整数 n , k n,k n,k,求 G ( n , k ) = ∑ i = 1 n k m o d    i G(n,k) = \sum_{i=1}^n k \mod i G(n,k)=i=1nkmodi

分析

  • 对于取模运算,可以转化: ∑ i = 1 n k m o d    i = ∑ i = 1 n k − i × ⌊ k i ⌋ = n × k − ∑ i = 1 n i × ⌊ k i ⌋ \sum_{i=1}^n k \mod i=\sum_{i=1}^n k-i\times \left\lfloor\dfrac k i\right\rfloor=n\times k-\sum_{i=1}^n i\times \left\lfloor\dfrac k i\right\rfloor i=1nkmodi=i=1nki×ik=n×ki=1ni×ik
  • 对于后面一项,我们发现它与 ∑ i = 1 n f ( i ) g ( ⌊ n i ⌋ ) \sum_{i=1}^nf(i)g(\left\lfloor\dfrac n i\right\rfloor) i=1nf(i)g(in) 形式上十分接近,可以考虑整除分块,不同的是分子部分为 k k k 而不是 n n n
  • 考虑 n > k n>k n>k,对于 ∀ i > k \forall i>k i>k,有 ⌊ k i ⌋ = 0 \left\lfloor\dfrac k i\right\rfloor=0 ik=0,故 ∑ i = 1 n i × ⌊ k i ⌋ = ∑ i = 1 k i × ⌊ k i ⌋ + ∑ i = k + 1 n i × ⌊ k i ⌋ = ∑ i = 1 k i × ⌊ k i ⌋ + 0 = ∑ i = 1 k i × ⌊ k i ⌋ \sum_{i=1}^n i\times \left\lfloor\dfrac k i\right\rfloor=\sum_{i=1}^k i\times \left\lfloor\dfrac k i\right\rfloor+\sum_{i=k+1}^n i\times \left\lfloor\dfrac k i\right\rfloor=\sum_{i=1}^k i\times \left\lfloor\dfrac k i\right\rfloor+0=\sum_{i=1}^k i\times \left\lfloor\dfrac k i\right\rfloor i=1ni×ik=i=1ki×ik+i=k+1ni×ik=i=1ki×ik+0=i=1ki×ik,所以左边界枚举到 n , k n,k n,k 的较小值即可。同时,右边界不能超过 n n n

代码

void solve() {
	ll n, k; cin >> n >> k;
	ll res = 0;
	for(ll l=1,r;l<=min(n,k);l=r+1) {
		r = min(n,k/(k/l));
		ll h = k / l;
		res += h * (l+r)*(r-l+1)/2;
	}
	cout << n*k-res << "\n";
}
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值