Trick / 零散知识点收录

主要收录一些自己做题的时候遇到的一些比较巧妙的 Trick 以及一些感觉自己比较容易遗忘的知识点。

如果可以会一直更新。

整除分块(2026.7.23)

整除分块是一种针对含有向下取整除法 \(\left\lfloor \frac{n}{i} \right\rfloor\) 的求和式进行优化的算法技巧。

它基于一个重要的数论性质:对于固定的正整数 \(n\),随着自变量 \(i\)\([1,n]\) 上连续变化,函数值 \(\left\lfloor \frac{n}{i} \right\rfloor\) 的取值是分段常数,且这些取值不同的段数仅为 \(O(2\sqrt{n})\) 级。

具体的,对于一段连续的 \(i\)\(\left\lfloor \frac{n}{i} \right\rfloor\) 的值是相同的。比如 \(\lfloor 10/1 \rfloor,\lfloor 10/2 \rfloor,\lfloor 10/3 \rfloor,\lfloor 10/4 \rfloor,\lfloor 10/5 \rfloor,\lfloor 10/6 \rfloor,\lfloor 10/7 \rfloor,\lfloor 10/8 \rfloor,\lfloor 10/9 \rfloor,\lfloor 10/10 \rfloor\) 都等于 1。并且这种块从 \(1\)\(n\) 最多只有 \(2\sqrt{n}\) 个。

所以我们就可以利用这个性质尝试算出每个块的右区间并跳过以此达到 \(O(\sqrt{n})\) 级别的时间复杂度。

那么具体怎么算呢?我先给个结论推导(或者说证明)可以选择看或不看:

结论:我们令块的左边界为 \(l\),右边界为 \(r\),则 $ r = \left\lfloor \frac{n}{\left\lfloor n/l \right\rfloor} \right\rfloor. $

推导

令块的左边界为 \(l\) ,右边界为 \(r\)\(x\) 是块里的一个整数下标, \(k = \lfloor n / l \rfloor\)

首先在同一个块里,对于 \(\lfloor n / x \rfloor\) 这个式子,它的值是随着 \(x\) 的上升而下降的。那么 \(k\) 也是随着 \(x\) 的上升而下降的。

所以事实上我们所说的右边界 \(r\) ,即最后面的满足 \(\lfloor n / x \rfloor = k\) 的位置 ,它实际上还是最后面的满足 \(\lfloor n / x \rfloor \ge k\) 的位置。

所以实际上 \(r\) 就是满足这个不等式: \(\left\lfloor \frac{n}{x} \right\rfloor \ge k\) 的最大的 \(x\)

但是这个下取整有点难办。我们可以尝试去掉这个下取整。

注意到如果 \(\left\lfloor \frac{n}{x} \right\rfloor \ge k\) ,那么 \(\frac{n}{x}\) 一定 \(\ge k\) 。因为 \(\left\lfloor \frac{n}{x} \right\rfloor\) 一定 \(\le \frac{n}{x}\)

所以我们可以得出 \(\frac{n}{x} \ge k\)

我们对其进行转化,可以推导出 \(\left\lfloor \frac{n}{k} \right\rfloor \ge x\)

又因为 \(x\) 是正整数,所以 \(x\) 最大为 \(\frac{n}{k}\)

\(k = \lfloor n / l \rfloor\) 代回去,得

$ r = \left\lfloor \frac{n}{\lfloor n/l \rfloor} \right\rfloor $

接下来,我们来道例题试试水。

Example

原题链接

题意概述

求出 \(1 \sim n\) 所有数的约数个数的和。

solution

事实上因为这道题里的 \(n \le 1000000\) ,所以完全不需要整除分块的 \(O(\sqrt n)\) 。但是确实是整除分块的一道练习好题。

考虑拆贡献。每个数对答案的贡献相当于其 \(\le n\) 的倍数的个数,也就是 \(n / i\)

所以我们可以枚举所有 \(i\) ,计算 \(n/i\) 。这里可以用整除分块加速。

整除分块的实现还是很简单的,不长,重在理解。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
signed main(){
    cin>>n;
    int res=0;
    for(int i=1;i<=n;i=n/(n/i)+1)res+=(n/(n/i)-i+1)*(n/i);
    cout<<res;
    return 0;
}

附上一个美味的整除分块题单

posted @ 2026-07-23 10:55  fireclouds_11  阅读(2)  评论(0)    收藏  举报