目录
引入
整除分块是数论问题中非常常用的技巧,先来看一个简单的问题:
已知,给定一个 n,求
的值。
先假设 ,第一反应肯定是
遍历一遍 1 ~ n 直接求和,轻松得到答案。
但如果 ,甚至
呢,显然就不能
暴力了,这就得用到接下来要介绍的整除分块的内容。
找规律
我们先试着计算一下 前几项(几十项)的值,比如 n = 20:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | |
20 | 10 | 6 | 5 | 4 | 3 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
可以发现 的取值在连续的一段区间内是相同的,那么就启发我们是否可以将其分为若干块分别进行计算呢,这就是整除分块的核心思想了。
分块数量(时间复杂度分析)
根据数学知识可以知道 的值的集合大小最多为
,我们可以简单证明一下这个数学现象:
对于正整数 ,当
时,由于
最多有
种取值,
最多也只能有
种取值;
当 时,由于
之后,
的值始终为0,而在
时
最多也只有
种取值,
最多也只能有
种取值。
综上所述, 的值的集合大小最多为
,这样整除分块的时间复杂度就为
。
分块边界
先让 为分块的左边界,那么这块的单个值
,这样右边界
就是值为
的最大下标
,也就是说要找满足
的最大的
,即
,将
代入,得到
。
这样每块的左右边界都可以用一个确定的式子计算得到,这样分块的值就为单值 × 区间长度,即。
模板
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]约数研究 - 洛谷
解题思路:根据约数的一个基本性质,每个正约数 在 1 ~ 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);
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.约数和
题目链接:约数和 - 洛谷
解题思路:和上一题类似,能得到每个正约数 在 1 ~ 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;
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]余数求和 - 洛谷
解题思路:根据余数和整除的性质,,就可以将题目式子转换成
,这样这个式子的后半部分就和上一题的基本相同了,这里就不再进行推导了。
本题必须要注意边界条件,当 时,随着
的增加,
始终为 0,因此需要退出分块计算。如果不退出就会由于计算
时分母为 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
题目大意:求给定区间 中每个数的正约数和之和。
解题思路:同上面第 2 题,只是数据范围增加到了 ,这里就需要开 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;
}