整除分块入门
整除分块是数论中的一个技巧,个人认为最好的理解方法是根据板题/例题解释。下面直接放了三道例题。
例1
题意
求 $\sum_{i=1}^n\lfloor \frac{n}{i} \rfloor$ 。
这道题的 $n$ 是 $int$ 范围内的非负数。
举个例子, $n=15$ ,给个直观一点的表:
$i$ | $1$ | $2$ | $3$ | $4$ | $5$ | $6$ | $7$ | $8$ | $9$ | $10$ | $11$ | $12$ | $13$ | $14$ | $15$ |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$\lfloor \frac{n}{i} \rfloor$ | $15$ | $7$ | $5$ | $3$ | $3$ | $2$ | $2$ | $1$ | $1$ | $1$ | $1$ | $1$ | $1$ | $1$ | $1$ |
把下面的所有的 $\lfloor \frac{n}{i} \rfloor$ 相加即答案。
思路
假设当前贡献值的左边界为 $l$ ,并且假设对于左边界 $l$ 存在一个右边界 $r$ 。
那么能得到:$$ \lfloor \frac{n}{l} \rfloor=\lfloor \frac{n}{l+1} \rfloor=\lfloor \frac{n}{l+2} \rfloor=...=\lfloor \frac{n}{l+k} \rfloor=...=\lfloor \frac{n}{r-1} \rfloor=\lfloor \frac{n}{r} \rfloor $$ 如果是暴力的话,这些同样的值就要加 $r-l+1$ 遍,稳稳的 $TLE$ 。
所以我们考虑能不能通过确定了 $l$ 求出 $r$ ,然后拿区间长度 $r-l+1$ 乘上一个 $\lfloor \frac{n}{l} \rfloor$。
所以我们通过向下取整的性质得出:$$ \lfloor \frac{n}{l} \rfloor \leq \frac{n}{r} $$ 根据不等式的性质可得:$$ r \leq \Bigg\lfloor \frac{n}{\lfloor \frac{n}{l} \rfloor} \Bigg\rfloor $$
$$ r_{max} = \Bigg\lfloor \frac{n}{\lfloor \frac{n}{l} \rfloor} \Bigg\rfloor $$
然后每次当前的 $l$ 等于上一次的 $r$ 加 $1$ 即可。
时间复杂度
$O(\sqrt n)$ 。
因为 $\lfloor \frac{n}{i} \rfloor$ 这玩意儿的集合大小最多为 $2 \sqrt n$ 。
分情况讨论, $i \leq \sqrt n$ 时集合大小最大为 $\sqrt n$ , $\sqrt n < i \leq n$ 时最大为 $\sqrt n$ 。
第一种 $i$ 最多只有 $\sqrt n$ 个;第二种是因为考虑 $n/i<=\sqrt n$ ,所以最多也只有 $\sqrt n$ 个。
而集合里每个元素对时间复杂度的贡献是是 $O(1)$ 。
所以接下来的东西就显而易见了。
#include <bits/stdc++.h>
#define ll long long
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
int T, n; ll ans;
int main(){
scanf("%d", &T);
while(T--){
scanf("%d", &n), ans = 0;
for(int l = 1, r; l <= n; l = r + 1){
r = n / (n / l);
ans += (r - l + 1) * (n / l);
}
printf("%lld\n", ans);
}
return 0;
}
例2
思路
每个正整数 $i$ 在 $1$ 到 $n$ 中作为因子出现的次数为 $\lfloor \frac{n}{i} \rfloor$ 。所以问题转换成了求 $\sum_{i=1}^ni*\lfloor \frac{n}{i} \rfloor$ 。
那么对于 $\lfloor \frac{n}{i} \rfloor$ 相同的,利用等差数列求和对 $l$ 到 $r$ 求和即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll h(const ll n){
ll ans = 0;
for(register ll l = 1, r; l <= n; l = r + 1){
r = n / (n / l);
ans += (l + r) * (r - l + 1) / 2 * (n / l);
}
return ans;
}
int main(){
ll l, r;
scanf("%lld%lld", &l, &r);
printf("%lld\n", h(r) - h(l - 1));
return 0;
}