【数论】整除分块(数论分块)

目录

引入

找规律

分块数量(时间复杂度分析)

分块边界

模板

例题

1.约束研究

2.约数和

3.余数求和

4.Fear Factoring

5.Floor and Mod


引入

整除分块是数论问题中非常常用的技巧,先来看一个简单的问题:

已知f(n) = \sum_{i = 1}^{n} \left \lfloor \frac{n}{i} \right \rfloor,给定一个 n,求 f(n) 的值。

先假设 1 \leqslant n \leqslant 10^6,第一反应肯定是 O(n) 遍历一遍 1 ~ n 直接求和,轻松得到答案。

但如果 1 \leqslant n \leqslant 10^9 ,甚至 1 \leqslant n \leqslant 10^{12} 呢,显然就不能 O(n) 暴力了,这就得用到接下来要介绍的整除分块的内容。

找规律

我们先试着计算一下 \left \lfloor \frac{n}{i} \right \rfloor 前几项(几十项)的值,比如 n = 20:

i1234567891011121314151617181920
\left \lfloor \frac{n}{i} \right \rfloor2010654322221111111111

可以发现 \left \lfloor \frac{n}{i} \right \rfloor 的取值在连续的一段区间内是相同的,那么就启发我们是否可以将其分为若干块分别进行计算呢,这就是整除分块的核心思想了。

分块数量(时间复杂度分析)

根据数学知识可以知道 \left \lfloor \frac{n}{i} \right \rfloor 的值的集合大小最多为 2\sqrt{n},我们可以简单证明一下这个数学现象:

对于正整数 i,当 i \leqslant \sqrt{n} 时,由于 i 最多有 \sqrt{n} 种取值,\left \lfloor \frac{n}{i} \right \rfloor 最多也只能有 \sqrt{n} 种取值;

当 i > \sqrt{n} 时,由于 i > n 之后,\left \lfloor \frac{n}{i} \right \rfloor 的值始终为0,而在 \sqrt{n} < i \leqslant n 时 i 最多也只有 \sqrt{n} 种取值,\left \lfloor \frac{n}{i} \right \rfloor 最多也只能有 \sqrt{n} 种取值。

综上所述, \left \lfloor \frac{n}{i} \right \rfloor 的值的集合大小最多为 2\sqrt{n},这样整除分块的时间复杂度就为 O(\sqrt{n})

分块边界

先让 l 为分块的左边界,那么这块的单个值 k = \left \lfloor \frac{n}{l} \right \rfloor,这样右边界 r 就是值为 k 的最大下标 i,也就是说要找满足 i \leqslant \frac{n}{k} 的最大的 i,即 r = max(i) = \left \lfloor \frac{n}{k} \right \rfloor,将 k 代入,得到 r = \left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor 。

这样每块的左右边界都可以用一个确定的式子计算得到,这样分块的值就为单值 × 区间长度,即k * (r - l + 1)

模板

ll division_block(ll n){
    ll res = 0;
    for(ll l = 1, r; l <= n; l = r + 1){
        r = n / (n / l);
        res += n / l * (r - l + 1);
    }
    return res;
}

例题

1.约束研究

题目链接:[AHOI2005]约数研究 - 洛谷

解题思路:根据约数的一个基本性质,每个正约数 i 在 1 ~ n 中出现的次数为 \left \lfloor \frac{n}{i} \right \rfloor ,问题转换成求\sum_{i = 1}^{n}\left \lfloor \frac{n}{i} \right \rfloor 的值,套用整除分块模板即可。

AC代码:

#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define mid (l + r >> 1)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
const int INF = 0x3f3f3f3f;
const ll LLF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int n, ans = 0;;
    cin >> n;
    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;
}

2.约数和

题目链接:约数和 - 洛谷

解题思路:和上一题类似,能得到每个正约数 i 在 1 ~ n 中出现的次数为 \left \lfloor \frac{n}{i} \right \rfloor ,那么每个约数对总和的贡献就为 i * \left \lfloor \frac{n}{i} \right \rfloor ,因此问题就转换成了求 \sum_{i = 1}^{n}i * \left \lfloor \frac{n}{i} \right \rfloor ,就可以分块处理了。

对于每块 [l, r] ,\left \lfloor \frac{n}{i} \right \rfloor 的值是相同的,可以认为是个常数,那么每块的计算就相当于求 \left \lfloor \frac{n}{l} \right \rfloor\sum_{i = l}^{r}i,这个用等差数列的求和就能够很轻易地得到答案。

最后注意求的是 [x, y] 区间的答案,可以转换成求 ans_{[1, r]} - ans_{[1, l - 1]}

AC代码:

#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define mid (l + r >> 1)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
const int INF = 0x3f3f3f3f;
const ll LLF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;

ll cal(int n){
    ll res = 0;
    for(ll l = 1, r; l <= n; l = r + 1){
        r = n / (n / l);
        res += n / l * (l + r) * (r - l + 1) / 2;
    }
    return res;
}

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    ll x, y;
    cin >> x >> y;
    cout << cal(y) - cal(x - 1) << endl;
    return 0;
}

3.余数求和

题目链接:[CQOI2007]余数求和 - 洛谷

解题思路:根据余数和整除的性质,k \; % \; i = k - \left \lfloor \frac{k}{i} \right \rfloor * i,就可以将题目式子转换成 \sum_{i = 1}^{n}(k - \left \lfloor \frac{k}{i} \right \rfloor * i) = \sum_{i = 1}^{n}k - \sum_{i = 1}^{n}\left \lfloor \frac{k}{i} \right \rfloor * i = nk - \sum_{i = 1}^{n}\left \lfloor \frac{k}{i} \right \rfloor * i,这样这个式子的后半部分就和上一题的基本相同了,这里就不再进行推导了。

本题必须要注意边界条件,当 k / i = 0 时,随着 i 的增加,k / i 始终为 0,因此需要退出分块计算。如果不退出就会由于计算 r = \left \lfloor \frac{k}{\left \lfloor \frac{k}{l} \right \rfloor} \right \rfloor 时分母为 0,程序出现错误。 同时注意分到最后一块时,计算出来的右边界可能超出 n,而显然此时的右边界就是 n,进行处理一下即可。

AC代码:

#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define mid (l + r >> 1)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
const int INF = 0x3f3f3f3f;
const ll LLF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    ll n, k;
    cin >> n >> k;
    ll ans = n * k;
    for(ll l = 1, r; l <= n; l = r + 1){
        if(k / l == 0) break;
        r = min(k / (k / l), n);
        ans -= k / l * (l + r) * (r - l + 1) / 2;
    }
    cout << ans << endl;
    return 0;
}

4.Fear Factoring

题目来源:​​​​​​Dashboard - 2017-2018 ACM-ICPC Pacific Northwest Regional Contest (Div. 2) - Codeforces

题目大意:求给定区间 [a, b] 中每个数的正约数和之和。

解题思路:同上面第 2 题,只是数据范围增加到了 10^{12},这里就需要开 unsigned long long,不然就会爆 long long。

AC代码:

#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define mid (l + r >> 1)
 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
const int INF = 0x3f3f3f3f;
const ll LLF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
 
ull cal(ull x){
    ull res = 0;
    for(ull l = 1, r; l <= x; l = r + 1){
        r = x / (x / l);
        res += x / l * (l + r) * (r - l + 1) / 2;
    }
    return res;
}
 
int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    ull a, b;
    cin >> a >> b;
    cout << cal(b) - cal(a - 1) << endl;
    return 0;
}

5.Floor and Mod

题目链接:Problem - 1485C - Codeforces

解题思路:

AC代码:

#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define mid (l + r >> 1)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
const int INF = 0x3f3f3f3f;
const ll LLF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;

int main(){
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t;
    cin >> t;
    while(t--){
        int x, y, b;
        cin >> x >> y;
        ll ans = 0;
        for(b = 1; b * b + b - 1 <= x && b <= y; b++){
            ans += (b * b + b - 1) / (b + 1);
        }
        for(int l = b, r; l <= y; l = r + 1){
            if(l + 1 > x) break;
            r = min(x / (x / (l + 1)) - 1, y);
            ans += x / (l + 1) * (r - l + 1);
        }
        cout << ans << endl;
    }
    return 0;
}
  • 16
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值