2024-18-19 ·最后更新时间:2024-18-19
1
,
s
o
l
u
t
i
o
n
(
1
)
\Large\mathcal{1,solution(1)}
1,solution(1)
如果我们把数
n
n
n 与小于等于
n
n
n 的数
i
i
i 的对应关系打印在表格上会是这样。
i i i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
⌊ n i ⌋ \Large\lfloor\frac{n}{i}\rfloor ⌊in⌋ | 10 | 5 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 |
可以发现 ⌊ n i ⌋ \Large\lfloor\frac{n}{i}\rfloor ⌊in⌋ 的数是有重复的,那么我们可不可以根据重复的特点来计算 ⌊ n i ⌋ \Large\lfloor\frac{n}{i}\rfloor ⌊in⌋ 的数值呢?答案是可以的,这也就是数论分块的主要思想。
2
,
v
e
r
i
f
i
c
a
t
i
o
n
\Large\mathcal{2,verification}
2,verification
为了保证算法的正确性我们要对其进行时间复杂度分析。
- 当 i > n i>\sqrt{n} i>n 时,那么此时 ⌊ n i ⌋ \Large\lfloor\frac{n}{i}\rfloor ⌊in⌋ 最多有 n \sqrt{n} n 种取值。
- 当 i ≤ n i\leq\sqrt{n} i≤n 时,那么此时 ⌊ n i ⌋ \Large\lfloor\frac{n}{i}\rfloor ⌊in⌋ 也最多有 n \sqrt{n} n 种取值。
所以最终我们可以得到时间复杂度为 O ( n ) O(\sqrt{n}) O(n)
3
,
s
o
l
u
t
i
o
n
(
2
)
\Large\mathcal{3,solution(2)}
3,solution(2)
接下来我们将要对边界进行分析,判断什么时候当前在哪一个块中。
首先我们假设当前块的左端点是 l l l ,那么根据我们之前的分析 ⌊ n l ⌋ \lfloor\frac{n}{l}\rfloor ⌊ln⌋ 就一定是我们当前块的数值,那么右端点该怎么求,对右端点 r r r 进行分析,其本质上就是满足 ⌊ n l ⌋ = ⌊ n r ⌋ \lfloor\frac{n}{l}\rfloor=\lfloor\frac{n}{r}\rfloor ⌊ln⌋=⌊rn⌋ 的 r r r 的最大值,等等,那不就是说明了 r = ⌊ n ⌊ n l ⌋ ⌋ r={\Large\lfloor}\frac{n}{\lfloor\frac{n}{l}\rfloor}\Large\rfloor r=⌊⌊ln⌋n⌋ 吗!?
那么这样我们就顺利的得到了 r r r 的值并且可以求出当前块的所有数的和,也就是 S = ( r − l + 1 ) × ⌊ n l ⌋ S=(r-l+1)×\lfloor\frac{n}{l}\rfloor S=(r−l+1)×⌊ln⌋
4 , c o d e \Large\mathcal{4,code} 4,code
int division_block(int n){
int ans=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=n/l*(r - l + 1);
}
return ans;
}
5
,
e
x
a
m
p
l
e
\Large\mathcal{5,example}
5,example
题目连接:[CQOI2007] 余数求和
思路:因为
a
m
o
d
b
a \mod b
amodb 可以转换为
a
−
⌊
a
b
⌋
b
a-\lfloor\frac{a}{b}\rfloor b
a−⌊ba⌋b 所以原式也就转化成了
∑
i
=
1
n
k
−
⌊
k
i
⌋
i
{\Large\sum_{i=1}^n} k-\lfloor\frac{k}{i}\rfloor i
∑i=1nk−⌊ik⌋i 可以发现
k
k
k 不会随着
i
i
i 的变化而变化,所以可以将
k
k
k 放在西格玛外面,
n
k
−
∑
i
=
1
n
⌊
k
i
⌋
i
nk-{\Large\sum_{i=1}^n}\lfloor\frac{k}{i}\rfloor i
nk−∑i=1n⌊ik⌋i 于是在这么多奇妙转化下,我们可以发现式子最后不就变成了数论分块的模板吗!?所以直接套模板就行,细节看代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,ans;
signed main(){
cin>>n>>k;
for(int l=1,r;l<=n;l=r+1){
r=k/l==0?n:min(n,k/(k/l));
ans+=(k/l)*(l+r)*(r-l+1)/2;
}
cout<<n*k-ans<<endl;
return 0;
}
代码有惊喜 q w q \color{white}{代码有惊喜qwq} 代码有惊喜qwq
安利一下我的博客qwq