整除分块入门

整除分块是数论中的一个技巧,个人认为最好的理解方法是根据板题/例题解释。下面直接放了三道例题。

例1

UVA11526 H(n)

题意

求 $\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

洛谷P2424

思路

每个正整数 $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;
}

习题1

洛谷P2261

posted @ 2023-01-24 11:39  徐子洋  阅读(12)  评论(0)    收藏  举报  来源