关于整除分块

整除分块

  大概要是长成这样的就能用到整除分块:
∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^n \left\lfloor \frac ni \right\rfloor i=1nin

  计算这个东西用 O ( n ) O(n) O(n) 的复杂度是显然能解决的,但是在某些考大家思维 毒瘤 的题目里面,就会把你这种做法给卡掉。这时候我们就可以用整除分块来把这个东西的复杂度降到 O ( n ) O(\sqrt{n}) O(n )

思想

  我们可以通过打表 (证明) 发现许多的 ⌊ n i ⌋ \left\lfloor \frac ni \right\rfloor in 的值时相同的,而且相同的值都聚集在一起。再通过一些神奇的方法 (打表) 可以发现对于每一个值相等的块,他的最后一个数的下标就是 ⌊ n ⌊ n i ⌋ ⌋ \left\lfloor\frac{n}{\lfloor\frac ni \rfloor}\right\rfloor inn,有了这个结论之后我们就可有 O ( n ) O(\sqrt{n}) O(n ) 计算 ∑ i = 1 n ⌊ n i ⌋ \sum\limits_{i=1}^n \left\lfloor \frac ni \right\rfloor i=1nin 了。

  打表用代码:

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'

int main(){
	freopen("a.out", "w", stdout);
	int n = 100;
	for(int i = 1; i <= n; i++){
		cout << n / i << endl;
	}
	return 0;
}

1 ∼ 100 1 \sim 100 1100 打出来是这个效果:
100
50
33
25
20
16
14
12
11
10
9
8
7 7
6 6
5 5 5 5
4 4 4 4 4
3 3 3 3 3 3 3 3
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

代码

  整除分块求 ∑ i = 1 n ⌊ n i ⌋ \sum\limits_{i=1}^n \left\lfloor \frac ni \right\rfloor i=1nin 的代码。

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'

int main(){
	int n = 100;
	int ans = 0;
	for(int l = 1, r; l <= n; l = r + 1){
		r = n / (n / l);
		ans += (n / l) * (r - l + 1);
	}
	cout <<  ans << endl;
	return 0;
}

证明:

  在前面的部分,我们通过打表 + 瞪眼观察法得到了如果一个区间的开头下表是 i i i,那么这个区间的结尾下标就是 ⌊ n ⌊ n i ⌋ ⌋ \left\lfloor\frac{n}{\lfloor\frac ni \rfloor}\right\rfloor inn,现在我们就来证明一下这个结论。

  首先假设一个区间开头为 i i i ,且令 k = ⌊ n i ⌋ k = \lfloor \frac ni \rfloor k=in,那么我们就有:
n = k i + r r ∈ [ 0 , i ) n = ki + r \qquad r \in [0, i) n=ki+rr[0,i)

  现在我们又凭空捏造出一个数 d d d,让:
k = ⌊ n i + d ⌋ k = \left\lfloor \frac{n}{i + d} \right\rfloor k=i+dn

 ( 这样我们就只需要证明 d 的取值范围就可以了)然后我们就又可以得到:
n = k i + k d + r ′ r ′ ∈ [ 0 , i + d ) n = ki + kd + r' \qquad r' \in [0, i + d) n=ki+kd+rr[0,i+d)

  我们联立上下两式:
k i + k d + r ′ = k i + r → k d + r ′ = r → r ′ = r − k d ≥ 0 ki + kd + r' = ki + r \rightarrow kd + r' = r \rightarrow r' = r - kd \geq 0 ki+kd+r=ki+rkd+r=rr=rkd0

  所以:
r − k d ≥ 0 → r ≥ k d → d ≤ r k r - kd \geq 0 \rightarrow r \geq kd \rightarrow d \leq \frac rk rkd0rkddkr

  这样我们就把 d 的取值范围顺利的算出来了。然后:
d m a x = ⌊ r k ⌋ d_{max} = \left\lfloor \frac rk \right\rfloor dmax=kr

  然后我们也得到了 i 的范围( i m a x i_{max} imax 是右端点):
i m a x = i + d m a x = i + ⌊ r k ⌋ = ⌊ i + r k ⌋ = ⌊ i k + r k ⌋ i_{max} = i + d_{max} = i + \left\lfloor \frac rk \right\rfloor = \left\lfloor i + \frac rk \right\rfloor = \left\lfloor \frac {ik+r}k \right\rfloor imax=i+dmax=i+kr=i+kr=kik+r

  又因为 i k + r = n ik + r = n ik+r=n k = ⌊ n i ⌋ k = \lfloor \frac ni \rfloor k=in,所以:
i m a x = ⌊ i k + r k ⌋ = ⌊ n ⌊ n i ⌋ ⌋ i_{max} = \left\lfloor \frac {ik+r}k \right\rfloor = \left\lfloor \frac {n}{\left\lfloor \frac ni \right\rfloor} \right\rfloor imax=kik+r=inn

  然后就证毕啦。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值