文章目录
什么是整除分块?
在我们求 ⌊ n i ⌋ \lfloor \frac n i \rfloor ⌊in⌋ 时会发现,当 i i i 不同时 ⌊ n i ⌋ \lfloor \frac n i \rfloor ⌊in⌋ 却可能相同。
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
15 i \frac {15} {i} i15 | 15 | 7 | 4 | 3 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
如果我们要求 ∑ i = 1 15 15 i \sum_{i=1}^{15} \frac{15}{i} ∑i=115i15,如果直接枚举 i,那么时间复杂度是 O(n) 的。
而如上图所示,存在相邻的若干项的值是相同的,我们是否可以每次把相同值的区间算出来,用 区间长度 * 区间中的值
便是这段区间中所有值之和了。
这样就不需要遍历区间中重复值的位置,而
⌊
n
i
⌋
\lfloor \frac n i \rfloor
⌊in⌋ 中不同值有
2
n
2\sqrt n
2n 种,故复杂度降为
O
(
n
)
O(\sqrt n)
O(n)。
证明:
对于
i
≤
⌊
n
⌋
,
⌊
n
i
⌋
i \leq\lfloor\sqrt{n}\rfloor ,\left\lfloor\frac{n}{i}\right\rfloor
i≤⌊n⌋,⌊in⌋ 最多有
⌊
n
⌋
\lfloor\sqrt{n}\rfloor
⌊n⌋ 种取值;
对于
i
>
⌊
n
⌋
i >\lfloor\sqrt{n}\rfloor
i>⌊n⌋ ,有
⌊
n
i
⌋
≤
⌊
n
⌋
\left\lfloor\frac{n}{i}\right\rfloor \leq\lfloor\sqrt{n}\rfloor
⌊in⌋≤⌊n⌋ ,最多也只有
⌊
n
⌋
\lfloor\sqrt{n}\rfloor
⌊n⌋ 种取值;
所以
⌊
n
i
⌋
\lfloor \frac n i \rfloor
⌊in⌋ 最多只有
2
n
2\sqrt n
2n 种取值。
(准确来说是
4
n
+
1
−
1
\sqrt{4n+1} -1
4n+1−1 种取值)
那么我们就可以从前往后枚举,每到一个位置,找到其所在相同值区间的最右端位置,两位置相减+1便是区间长度,然后跳过这个区间。
那么如何得到相同值区间最右端位置呢?
设一段区间
[
l
,
r
]
[l, r]
[l,r] 中所有元素的值都为
k
=
⌊
n
l
⌋
k = \lfloor \frac n l \rfloor
k=⌊ln⌋。
现在要找到最大的
i
i
i 使得
k
=
⌊
n
i
⌋
k = \lfloor \frac n i \rfloor
k=⌊in⌋ 满足。
也就是,找到满足
i
≤
n
k
i \leq \frac n k
i≤kn 的最大的
i
i
i,那么这个
i
i
i 为
⌊
n
k
⌋
\lfloor \frac n k \rfloor
⌊kn⌋,即
r
=
⌊
n
k
⌋
r = \lfloor \frac n k \rfloor
r=⌊kn⌋。
将
k
=
⌊
n
l
⌋
k = \left\lfloor \frac n l \right\rfloor
k=⌊ln⌋ 代入上式即得:
r
=
⌊
n
⌊
n
l
⌋
⌋
r = \left\lfloor {\frac {n}{\left\lfloor \frac n l \right\rfloor}} \right\rfloor
r=⌊⌊ln⌋n⌋。
所以我们只要在位置 l l l,就能得到和 ⌊ n l ⌋ \lfloor \frac n l \rfloor ⌊ln⌋ 值相同的区间最右端位置: r = ⌊ n ⌊ n l ⌋ ⌋ r = \left\lfloor {\frac {n}{\left\lfloor \frac n l \right\rfloor}} \right\rfloor r=⌊⌊ln⌋n⌋
那么对于求 ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor i=1∑n⌊in⌋ 就可以这样实现,复杂度 O ( n ) O(\sqrt n) O(n):
int ans = 0;
for(int l = 1, r; l <= n; l = r + 1) //l是枚举的i,r是相同值的最大i
{
r = n/(n/l);
ans += (r-l+1) * (n/l); //注意n/l的括号一定要加上,先整除再相乘
}
cout << ans;
下面来看几道例题:
例1:约数研究
Link
定义
f
(
N
)
f(N)
f(N) 为
N
N
N 的约数的个数。
求
∑
i
=
1
n
f
(
i
)
\sum_{i=1}^{n} f(i)
∑i=1nf(i)。
1 ≤ n ≤ 2 × 1 0 14 1≤ n ≤2×10^{14} 1≤n≤2×1014
思路
如果挨个求每个数的约数的话,复杂度是
O
(
n
n
)
O(n\sqrt n)
O(nn),会超时。
转化思路,考虑 1~n 每个约数分别出现了多少次?
对于一个数 x 来说,肯定作为其所有倍数的约数,那 1~n 中 x 的倍数有
⌊
n
x
⌋
\lfloor \frac n x \rfloor
⌊xn⌋ 个,那这个约数就出现
⌊
n
x
⌋
\lfloor \frac n x \rfloor
⌊xn⌋ 次。遍历所有数累加其出现的次数。
所以题目就转化为求:
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor
i=1∑n⌊in⌋
就是模板题了。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
signed main(){
Ios;
cin >> n;
int ans = 0;
for(int l = 1, r; l <= n; l = r + 1)
{
r = n/(n/l);
ans += (r-l+1) * (n/l);
}
cout << ans;
return 0;
}
例2:P2424 约数和
Link
定义
f
(
N
)
f(N)
f(N) 为
N
N
N 的约数之和。
给定
X
,
Y
X,Y
X,Y,求
f
(
X
)
+
f
(
X
+
1
)
+
⋯
+
f
(
Y
)
f(X)+f(X+1)+\dots+f(Y)
f(X)+f(X+1)+⋯+f(Y)。
1 ≤ n ≤ 2 × 1 0 14 1≤ n ≤2×10^{14} 1≤n≤2×1014
思路
我们可以仿照上一题的思路求
∑
i
=
1
n
f
(
i
)
\sum_{i=1}^{n} f(i)
∑i=1nf(i),然后用
∑
i
=
1
Y
f
(
i
)
\sum_{i=1}^{Y} f(i)
∑i=1Yf(i) 减去
∑
i
=
1
X
−
1
f
(
i
)
\sum_{i=1}^{X-1} f(i)
∑i=1X−1f(i) 即可。
和上一题的思路一样,考虑每个约数的贡献。
对于每个约数 x,其出现了
⌊
n
x
⌋
\left\lfloor \frac n x \right\rfloor
⌊xn⌋ 次,那么这个约数之和就是
⌊
n
x
⌋
∗
x
\lfloor \frac n x \rfloor * x
⌊xn⌋∗x。
所以这题就转化为求:
∑
i
=
1
n
⌊
n
i
⌋
∗
i
\sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor * i
i=1∑n⌊in⌋∗i
对于一段区间 [l, r] 来说,其
⌊
n
i
⌋
\lfloor\frac{n}{i}\rfloor
⌊in⌋ 值相同,但是 i 值不同。
但由于 i 在同一区间,可以利用等差数列算出这一区间的 i 之和 sum,用 sum *
⌊
n
i
⌋
\lfloor\frac{n}{i}\rfloor
⌊in⌋ 便是这段区间的贡献
∑
i
=
l
r
⌊
n
i
⌋
∗
i
\sum_{i=l}^{r}\lfloor\frac{n}{i}\rfloor * i
∑i=lr⌊in⌋∗i。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int sum_1(int n)
{
return n * (n+1) / 2;
}
int ans(int n)
{
int ans = 0;
for(int l=1, r; l<=n; l=r+1)
{
r = n/(n/l);
ans += (sum_1(r) - sum_1(l-1)) * (n/l);
}
return ans;
}
signed main(){
Ios;
cin >> n >> m;
cout << ans(m) - ans(n-1);
return 0;
}
例3:几番烟雾,只有花难护
Link
题目转化为,求:
∑
i
=
1
n
⌈
n
i
⌉
∗
i
2
\sum_{i=1}^{n}\left\lceil \frac{n}{i} \right\rceil * i^2
i=1∑n⌈in⌉∗i2
答案对
998244353
998244353
998244353 取模。
思路
上取整转化为下取整:
⌈
n
i
⌉
=
⌊
n
−
1
i
⌋
+
1
\left\lceil \frac{n}{i} \right\rceil = \left\lfloor \frac {n-1} i \right\rfloor +1
⌈in⌉=⌊in−1⌋+1
注意这个公式只能在保证 n 不为 0 的情况下使用,n = 0 时出现错误为 1。
当 n 可能为 0 时可以用公式
⌈
n
i
⌉
=
⌊
n
+
i
−
1
i
⌋
\left\lceil \frac{n}{i} \right\rceil = \left\lfloor \frac {n + i -1} i \right\rfloor
⌈in⌉=⌊in+i−1⌋。
于是原式转化为:
∑
i
=
1
n
⌊
n
−
1
i
⌋
∗
i
2
+
∑
i
=
1
n
i
2
\sum_{i=1}^{n}\left\lfloor\frac{n-1}{i} \right\rfloor* i^2 + \sum_{i=1}^{n} i^2
i=1∑n⌊in−1⌋∗i2+i=1∑ni2
后半部分可以直接求出,前半部分和上一题类似,这题需要处理出区间的
i
2
i^2
i2 之和,乘以区间相同值
⌊
n
i
⌋
\left\lfloor\frac{n}{i}\right\rfloor
⌊in⌋ 即可。
需要知道:
1
2
+
2
2
+
3
2
+
…
+
n
2
=
n
(
n
+
1
)
(
2
n
+
1
)
6
1^{2}+2^{2}+3^{2}+\ldots+\mathrm{n}^{2}=\frac{\mathrm{n}(\mathrm{n}+1)(2 \mathrm{n}+1)}{6}
12+22+32+…+n2=6n(n+1)(2n+1)
- 除法要用逆元。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
const int N = 200010, mod = 998244353;
int T, n, m;
int a[N];
int qmi(int x, int y)
{
int ans = 1;
while(y)
{
if(y & 1) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
int sum_2(int n)
{
return n * (n + 1) % mod * (2*n + 1) % mod * qmi(6, mod-2) % mod;
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
int ans = sum_2(n);
int x = n-1;
for(int l=1, r; l <= x; l = r+1)
{
r = x/(x/l);
int t = (sum_2(r) - sum_2(l-1) + mod) % mod;
ans = (ans + t * (x/l) % mod) % mod;
}
cout << ans << endl;
}
return 0;
}
此外还有
1
3
+
2
3
+
…
+
n
3
=
[
n
(
n
+
1
)
2
]
2
=
(
1
+
2
+
…
+
n
)
2
1^{3}+2^{3}+\ldots+\mathrm{n}^{3}=\left[\frac{\mathrm{n}(\mathrm{n}+1)}{2}\right]^{2}=(1+2+\ldots+\mathrm{n})^{2}
13+23+…+n3=[2n(n+1)]2=(1+2+…+n)2
例4:P2261 [CQOI2007] 余数求和
Link
给出正整数
n
n
n 和
k
k
k ,请计算
G
(
n
,
k
)
=
∑
i
=
1
n
k
m
o
d
i
G(n, k)=\sum_{i=1}^{n} k \bmod i
G(n,k)=i=1∑nkmodi
思路
k
m
o
d
i
k\bmod i
kmodi 不好处理,将其转化,等价于
k
−
i
∗
⌊
k
i
⌋
k - i* \left\lfloor \frac {k} {i} \right\rfloor
k−i∗⌊ik⌋。
原式转化为:
∑
i
=
1
n
k
−
i
∗
⌊
k
i
⌋
\sum_{i=1}^{n} k - i*\left\lfloor \frac k i \right\rfloor
i=1∑nk−i∗⌊ik⌋
即:
∑
i
=
1
n
k
−
∑
i
=
1
n
i
∗
⌊
k
i
⌋
\sum_{i=1}^{n} k - \sum_{i=1}^{n}i*\left\lfloor \frac k i \right\rfloor
i=1∑nk−i=1∑ni∗⌊ik⌋
前半部分可以直接算出为 n*k,后半部分和例2一样,利用前缀和相减 * 区间值。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int k;
int sum(int l, int r)
{
return (l+r)*(r-l+1)/2;
}
signed main(){
cin >> n >> k;
int ans = n * k;
for(int l = 1, r; l <= min(n, k); l = r + 1) //注意这里只需要循环到min(n,k)。否则当l>k时,k/l=0, k/0无意义
{
r = min(n, k/(k/l)); //这里的右端点要和n取min
ans -= sum(l, r) * (k/l);
}
cout << ans;
return 0;
}
当枚举的除数 i 的范围和原数 x 不相同的话,要注意两个地方:
- 枚举的除数 i 最大应该到 min(n, x)。因为当
i
>
x
i > x
i>x 时,
x/i
= 0,x/(x/i)
便会无意义。 - r 的范围最大不超过 n,要和 n 取 min。当 n 和 x 相同时,
n/(n/l)
一定不会超过 n,没问题。但是当 n 和 x 不同时,x/(x/l)
却可能大于 n,但是除数只需要枚举到 n,就加多了。
例5:求和公式
Link
给定
n
,
m
n, m
n,m 求:
∑
i
=
1
n
∑
j
=
1
m
i
2
j
(
n
%
i
)
(
m
%
j
)
\sum_{i=1}^{n} \sum_{j=1}^{m} i^{2} j(n \% i)(m \% j)
i=1∑nj=1∑mi2j(n%i)(m%j)
10 ≤ n , m ≤ 1 0 9 10 \leq n,m \leq 10^9 10≤n,m≤109,答案取模 1 e 9 + 7 1e9+7 1e9+7。
思路
首先观察出 i 和 j 没有联系,将其分开:
∑
i
=
1
n
i
2
(
n
%
i
)
∗
∑
j
=
1
m
j
(
m
%
j
)
\sum_{i=1}^{n} i^{2} (n \% i)*\sum_{j=1}^{m}j(m \% j)
i=1∑ni2(n%i)∗j=1∑mj(m%j)
将取模操作转化:
∑
i
=
1
n
i
2
(
n
−
i
∗
⌊
n
i
⌋
)
∗
∑
j
=
1
m
j
(
m
−
j
∗
⌊
m
j
⌋
)
\sum_{i=1}^{n} i^{2} \left(n - i* \left\lfloor \frac n i \right\rfloor\right)*\sum_{j=1}^{m}j \left(m - j*\left\lfloor \frac m j\right\rfloor\right)
i=1∑ni2(n−i∗⌊in⌋)∗j=1∑mj(m−j∗⌊jm⌋)
即:
[
n
∗
∑
i
=
1
n
i
2
−
∑
i
=
1
n
i
3
∗
⌊
n
i
⌋
]
∗
[
m
∗
∑
j
=
1
m
j
−
∑
j
=
1
m
j
2
∗
⌊
m
j
⌋
]
\left[n*\sum_{i=1}^{n} i^{2} - \sum_{i=1}^{n} i^3* \left\lfloor \frac n i\right\rfloor\right] * \left[ m*\sum_{j=1}^{m} j - \sum_{j=1}^{m} j^2*\left\lfloor \frac m j\right\rfloor\right]
[n∗i=1∑ni2−i=1∑ni3∗⌊in⌋]∗[m∗j=1∑mj−j=1∑mj2∗⌊jm⌋]
而对于左右两个部分的前半部分都可以直接求出,后半部分和上例一样,利用前缀和相减 * 区间相同值。
Code
//https://ac.nowcoder.com/acm/contest/2891/C
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
/*
尽量保证运算过程中的每一个小块在参与下一步运算时都是正数。
可以把每个小块单独拉出来写。
*/
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int qmi(int x, int y)
{
int ans = 1;
while(y)
{
if(y & 1) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
int sum_1(int n)
{
return n * (n+1) % mod * qmi(2, mod - 2) % mod;
}
int sum_2(int n)
{
return n * (n+1) % mod * (2*n + 1) % mod * qmi(6, mod - 2) % mod;
}
int sum_3(int n)
{
int x = n * (n+1) % mod * qmi(2, mod - 2) % mod;
return x * x % mod;
}
signed main(){
Ios;
cin >> n >> m;
int ans1 = n * sum_2(n) % mod;
for(int l = 1, r; l <= n; l = r + 1)
{
r = min(n, n/(n/l));
int t = (sum_3(r) - sum_3(l-1) + mod) % mod;
ans1 = (ans1 - t * (n/l) % mod + mod) % mod; //两数相乘之后一定先取模再进行其他操作
}
int ans2 = m * sum_1(m) % mod;
for(int l = 1, r; l <= m; l = r + 1)
{
r = min(m, m/(m/l));
int t = (sum_2(r) - sum_2(l-1) + mod) % mod;
ans2 = (ans2 - t * (m/l) % mod + mod) % mod;
}
cout << ans1 * ans2 % mod;
return 0;
}
多维整除分块(N 维数论分块)
求形如下式的和式的值: ∑ i = 1 n ⌊ a 1 i ⌋ ⌊ a 2 i ⌋ ⋯ ⌊ a m i ⌋ \sum_{i=1}^{n}\left\lfloor\frac{a_{1}}{i}\right\rfloor\left\lfloor\frac{a_{2}}{i}\right\rfloor \cdots\left\lfloor\frac{a_{m}}{i}\right\rfloor i=1∑n⌊ia1⌋⌊ia2⌋⋯⌊iam⌋
每段区间的右端点 r = min i = 1 m { [ a i ⌊ a i l ⌋ ⌋ } r=\min _{i=1}^{m}\left\{\left[\frac{a_{i}}{\left\lfloor\frac{a_{i}}{l}\right\rfloor}\right\rfloor\right\} r=mini=1m{[⌊lai⌋ai⌋} 即可,即取每一区间的右端点中最小的那个作为整体的右端点。
较常用的是 2 维数论分块:求
∑
i
=
1
n
⌊
n
i
⌋
⌊
m
i
⌋
\sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor\left\lfloor\frac{m}{i}\right\rfloor
i=1∑n⌊in⌋⌊im⌋ 区间右端点
r
=
min
(
⌊
n
⌊
n
l
⌋
⌋
,
⌊
m
⌊
m
l
⌋
⌋
)
r=\min\left(\left\lfloor\frac{n}{\left\lfloor\frac{n}{l}\right\rfloor}\right\rfloor,\left\lfloor\frac{m}{\left\lfloor\frac{m}{l}\right\rfloor}\right\rfloor\right)
r=min(⌊⌊ln⌋n⌋,⌊⌊lm⌋m⌋),即 r = min(n, n/(n/l), m/(m/l))
。
时间复杂度仍为 O ( n ) O(\sqrt n) O(n)。
例题:P2260 [清华集训2012] 模积和
Link
题意
求:
∑
i
=
1
n
∑
j
=
1
m
(
n
m
o
d
i
)
×
(
m
m
o
d
j
)
,
i
≠
j
\sum_{i = 1}^{n} \sum_{j = 1}^{m}(n \bmod i) \times(m \bmod j), i \neq j
i=1∑nj=1∑m(nmodi)×(mmodj),i=j
m o d 19940417 \bmod\ 19940417 mod 19940417 的值。
思路
乍一看和上一题的做法一样,将 i 和 j 分开分别处理就行了,但是要注意到一个条件:
i
≠
j
i \neq j
i=j。
可以用一个容斥,先不考虑相等的情况,分开 i 和 j 分别算出答案相乘之后再减去 i 和 j 相同情况的答案:
∑
i
=
1
n
(
n
m
o
d
i
)
∗
∑
j
=
1
m
(
m
m
o
d
j
)
−
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
m
o
d
i
)
(
m
m
o
d
i
)
\sum_{i=1}^{n} (n \bmod i)*\sum_{j=1}^{m}\left(m \bmod j\right) - \sum_{i=1}^{min(n, m)}\left(n \bmod i\right)\left(m \bmod i\right)
i=1∑n(nmodi)∗j=1∑m(mmodj)−i=1∑min(n,m)(nmodi)(mmodi)
前两项直接求解,最后一项化简:
∑
i
=
1
n
(
n
m
o
d
i
)
(
m
m
o
d
i
)
=
∑
i
=
1
n
(
n
−
⌊
n
i
⌋
i
)
(
m
−
⌊
m
i
⌋
i
)
=
∑
i
=
1
n
(
n
m
−
⌊
n
i
⌋
i
⋅
m
−
⌊
m
i
⌋
i
⋅
n
+
⌊
n
i
⌋
⌊
m
i
⌋
i
2
)
=
n
2
m
−
m
∑
i
=
1
n
⌊
n
i
⌋
i
−
n
∑
i
=
1
n
⌊
m
i
⌋
i
+
∑
i
=
1
n
⌊
n
i
⌋
⌊
m
i
⌋
i
2
\sum_{i=1}^{n}(n \bmod i)(m \bmod i) \\=\sum_{i=1}^{n}\left(n-\left\lfloor\frac{n}{i}\right\rfloor i\right)\left(m-\left\lfloor\frac{m}{i}\right\rfloor i\right) \\ =\sum_{i=1}^{n}\left(n m-\left\lfloor\frac{n}{i}\right\rfloor i \cdot m-\left\lfloor\frac{m}{i}\right\rfloor i \cdot n+\left\lfloor\frac{n}{i}\right\rfloor\left\lfloor\frac{m}{i}\right\rfloor i^{2}\right) \\ =n^{2} m-m \sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor i-n \sum_{i=1}^{n}\left\lfloor\frac{m}{i}\right\rfloor i+\sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor\left\lfloor\frac{m}{i}\right\rfloor i^{2}
i=1∑n(nmodi)(mmodi)=i=1∑n(n−⌊in⌋i)(m−⌊im⌋i)=i=1∑n(nm−⌊in⌋i⋅m−⌊im⌋i⋅n+⌊in⌋⌊im⌋i2)=n2m−mi=1∑n⌊in⌋i−ni=1∑n⌊im⌋i+i=1∑n⌊in⌋⌊im⌋i2
第 2,3 项都是模板,第 4 项就是一个 2 维数论分块。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
const int N = 200010, mod = 19940417;
int T, n, m;
int a[N];
int inv2 = 9970209, inv6 = 3323403;
int x, y;
void exgcd(int a, int b)
{
if (!b)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b);
int tmp = x;
x = y;
y = tmp - a / b * y;
}
int inv(int a)
{
exgcd(a, mod);
x = (x % mod + mod) % mod;
return x;
}
int sum_1(int n){
return n * (n+1) % mod * inv2 % mod;
}
int sum_2(int n){
return n * (n+1) % mod * (2*n+1) % mod * inv6 % mod;
}
int ans_1(int n, int x)
{
int ans = 0;
for(int l=1, r; l <= min(n, x); l = r+1)
{
r = min(n, x/(x/l));
int t = (sum_1(r) - sum_1(l-1) + mod) % mod;
ans = (ans + t * (x/l) % mod + mod) % mod;
}
return ans;
}
int ans_2(int n, int x, int y)
{
int ans = 0;
for(int l = 1, r; l <= n; l = r+1)
{
r = min(n, min(x/(x/l), y/(y/l)));
int t = (sum_2(r) - sum_2(l-1) + mod) % mod;
ans = (ans + t * (x/l) % mod * (y/l) % mod) % mod;
}
return ans;
}
signed main(){
Ios;
cin >> n >> m;
int res1 = (n * n % mod - ans_1(n, n) + mod) % mod;
int res2 = (m * m % mod - ans_1(m, m) + mod) % mod;
int mina = min(n, m);
int res3 = (n * m % mod * mina % mod - n * ans_1(mina, m) % mod - m * ans_1(mina, n) % mod
+ ans_2(mina, n, m) + mod) % mod;
cout << (res1 * res2 % mod - res3 + mod) % mod;
return 0;
}
注意在该题中,模数 19940417 不是素数,则不能用快速幂求逆元,需要用扩展欧几里得。
逆元只求了 2 和 6 两个数,为了降低复杂度,可以预处理出来。
总结:
总之,整除分块就是一个可以以 O ( n ) O(\sqrt n) O(n) 的复杂度求解形如 ∑ i = 1 n ⌊ a 1 i ⌋ ⌊ a 2 i ⌋ ⋯ ⌊ a m i ⌋ \sum_{i=1}^{n}\left\lfloor\frac{a_{1}}{i}\right\rfloor\left\lfloor\frac{a_{2}}{i}\right\rfloor \cdots\left\lfloor\frac{a_{m}}{i}\right\rfloor ∑i=1n⌊ia1⌋⌊ia2⌋⋯⌊iam⌋ 的问题的算法。
哪里有问题的话可以留言评论~
参考文章:
ACM竞赛中常见的整数公式与整数分块
OI Wiki - 数论分块
整数分块 —— 完美的根号n算法
【数论】整除分块(数论分块)
C -求和公式(整除分块)
【数学】数论分块(整除分块)
13+23+33+…+n3通项公式