笔记:整除分块
核心思想
整除分块是这样一个算法:
求
是怎么实现的呢?
首先引入一个重要的概念——决策点。
这个概念是怎么来的?对于每一个
| 1 | 10 |
| 2 | 5 |
| 3 | 3 |
| 4 | 2 |
| 5 | 2 |
| 6 | 1 |
| 7 | 1 |
| 8 | 1 |
| 9 | 1 |
| 10 | 1 |
容易发现,出现了很多的
其中被蓝色圈起来了的叫做决策点。
利用决策点,我们就可以对一段连续且相等的数进行统一处理。接下来,为了表述简洁,我们使用
容易得出一个式子:
其中
所以,整除分块的关键在于从
如何理解?首先,
于是乎,代码就很好写了。用一道例题给出代码:
例题 1
H(n)。
分析一下代码发现就是求
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
long long H(int n) {
long long res = 0;
for(int i = 1, j; i <= n; i = j) {
j = n / (n / i) + 1;
res += 1LL * (j - i) * (n / i);
}
return res;
}
int main() {
ios :: sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while(T--) {
int n;
cin >> n;
cout << H(n) << endl;
}
return 0;
}
同时,我们分析一下这个代码的时间复杂度。
显然,每一次循环会处理一个连续段,所以时间复杂度是
首先,对于
所以这个算法是
例题 2
余数求和。
首先发现题目要求的式子是
其中
好的,现在就是一个整除分块的标准形式了,只不过多乘了一个
这个变形就是等差数列求和公式。
所以代码就很好写啦~
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int n, k;
int main() {
ios :: sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> k;
long long ans = 1LL * n * k;
for(int i = 1, j; i <= n; i = j) {
if(k / i != 0) j = min(k / (k / i) + 1, n + 1);
else j = n + 1;
ans -= 1LL * (k / i) * (i + j - 1) * (j - i) / 2;
}
cout << ans << endl;
return 0;
}
例题 3
模积和。
这一道题的式子非常毒瘤,看似十分简单,实则暗藏五个整除分块。
首先,化简一下式子:
然后对于前半部分和后半部分分别处理。对于前半部分我们使用对于取模的经典套路,可以得到这个形式:
是
有三个整除分块。
不对啊,最后一个和式有两个向下取整啊?
这个东西叫二维整除分块。当求和里面有两个向下取整的时候,我们可以注意到决策点的数量还是
下一个决策点是
接着用二维等差数列的求和公式:
就可以很方便的得到答案了。代码实现有一点点复杂,注意我代码里的
#include <bits/stdc++.h>
#define int __int128
#define endl '\n'
using namespace std;
const int MOD = 19940417;
const int inv2 = 9970209;
const int inv6 = 3323403;
int n, m;
inline int read() {
int f = 1, x = 0;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -f; ch = getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
return f * x;
}
int get(int x) {
return x * (x + 1) * (2 * x + 1) * inv6 % MOD;
}
inline void write(int x) {
char buf[20];
int t = 0;
if(x < 0) { putchar('-'); x = -x; }
if(x == 0) { putchar('0'); return; }
while(x > 0) { buf[++t] = x % 10 + '0'; x /= 10; }
for(int i = t; i > 0; i--) putchar(buf[i]);
}
signed main() {
n = read(), m = read();
int a1 = 0, a2 = 0, a3 = 0, a4 = 0, a5 = 0;
for(int i = 1, j; i <= n; i = j + 1) {
j = n / (n / i);
a1 = (a1 + (j - i + 1) * (j + i) * inv2 * (n / i)) % MOD;
}
a1 = (n * n - a1) % MOD;
for(int i = 1, j; i <= m; i = j + 1) {
j = m / (m / i);
a2 = (a2 + (j - i + 1) * (j + i) * inv2 * (m / i)) % MOD;
}
a2 = (m * m - a2) % MOD;
for(int i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), min(n, m));
a3 = (a3 + (j - i + 1) * (j + i) * inv2 * (n / i)) % MOD;
}
a3 = a3 * m % MOD;
for(int i = 1, j; i <= min(n, m); i = j + 1) {
j = min(m / (m / i), min(n, m));
a4 = (a4 + (j - i + 1) * (j + i) * inv2 * (m / i)) % MOD;
}
a4 = a4 * n % MOD;
for(int i = 1, j; i <= min(n, m); i = j + 1) {
j = min(min(n / (n / i), m / (m / i)), min(n, m));
a5 = (a5 + (get(j) - get(i - 1)) * (n / i) * (m / i)) % MOD;
}
write((a1 * a2 - (min(n, m) * n * m - a3 - a4 + a5) % MOD + MOD) % MOD);
return 0;
}