整除分块(超快速通/超简单超易懂/详细解析)+ 模积和(算法详解/清晰代码)(看了的人rp++++++++)
这是作者小蒟第一篇发出来的博客
写的不好勿怪
感谢各位读者支持吖
(博客等级不够高,没办法自定义标签,如果在已有的标签里乱选的话又有点对不起读者。所以文章可能没那么容易被检索到。看到觉得有用好看的宝麻烦口口相传推荐给身边的伙伴吖)
前言
本蒟最近做了一道题 模积和 ,被迫去学了一下整除分块。那既然学了肯定要打下总结呀,想着放在文件夹里也是落灰(大概率也不会再看了),不如发出来 造福万民 与民同乐 和大家分享。
个人认为写得还算清楚,起码能让我自己看懂。因为作者本人也非常的菜,所以难度应该还是不会很高,适用于大部分的读者。
好啦好啦不废话啦,进入正题。
前置知识
分块
有请 oi-wiki
其实,分块是一种思想,而不是一种数据结构。
分块的基本思想是,通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。
分块其实蛮有用的,对于一些暴力过不了的题目,完全可以考虑分块,说不定可以卡过去😎
那么在整除分块这一知识点里面,就是把整除后值相同的分成一个块,从而将复杂度降到 O ( n ) \Omicron(\sqrt{n}) O(n)
例题引路
给出 n ,求 ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor ∑i=1n⌊in⌋
我们手膜一下 n = 20 n=20 n=20 时的情况
i | 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 |
看上表可知:可能会有连续的多个数
i
i
i ,
⌊
n
i
⌋
\lfloor \frac{n}{i} \rfloor
⌊in⌋ 的值相等。那么这个时候就可以用到分块,因为是整除(下取整),所以也就是整除分块(说了跟说了似的)
我们设每一块的左端点为 l l l,那么这一块的每个单值 k = ⌊ n i ⌋ k=\lfloor \frac{n}{i} \rfloor k=⌊in⌋ ,右端点就是 r = ⌊ n ⌊ n i ⌋ ⌋ r=\lfloor \frac{n}{ \lfloor \frac{n}{i} \rfloor} \rfloor r=⌊⌊in⌋n⌋
那么每个分块的值就是 k ( r − l + 1 ) k(r-l+1) k(r−l+1),答案累加即可。
因为 n 以内最多有 2 n 2\sqrt{n} 2n 个不同的数,所以时间复杂度就是 O ( n ) \Omicron(\sqrt{n}) O(n) 的(省略常数项)
给下关键代码:
int s=0;
for(int l=1,r;l<=n;l=r+1)
{
int k=n/l;
r=n/k;
s+=k*(r-l+1);
}
给道练习题
模积和
内容概括
给定 $ n,m $ ,求 ∑ i = 1 n ∑ j = 1 m ( n % i ) ( m % j ) i f ( i ≠ j ) \sum_{i=1}^{n} \sum_{j=1}^{m} (n\%i)(m\%j) \space \space \space \space if(i \not= j) ∑i=1n∑j=1m(n%i)(m%j) if(i=j)
其中 1 ≤ n , m ≤ 1 0 9 1 \le n,m \le 10^9 1≤n,m≤109
题目解法
显然这是 O ( n m ) \Omicron(nm) O(nm) 的
尝试将其优化至
O
(
n
)
\Omicron(n)
O(n),其实也很简单,就把两个
∑
\sum
∑ 拆开,变成1
∑
i
=
1
n
(
n
%
i
)
∑
i
=
1
m
(
m
%
i
)
−
∑
i
=
1
n
(
n
%
i
)
(
m
%
i
)
\sum_{i=1}^{n} (n\%i) \sum_{i=1}^{m} (m\%i)- \sum_{i=1}^{n} (n\%i)(m\%i)
i=1∑n(n%i)i=1∑m(m%i)−i=1∑n(n%i)(m%i)
(注意,因为 i ≠ j i \not= j i=j ,所以一定不要忘记减去最后的公共部分)
但是
对于100%的数据n,m<=10^9。
所以我们需要 O ( n ) \Omicron(\sqrt{n}) O(n) 算法
先拆柿子:我们知道 a % b a\%b a%b 等价于 a − ⌊ a b ⌋ b a-\lfloor \frac{a}{b} \rfloor b a−⌊ba⌋b
所以
∑
i
=
1
n
(
n
−
⌊
n
i
⌋
i
)
∑
i
=
1
m
(
m
−
⌊
m
i
⌋
i
)
−
∑
i
=
1
n
(
n
−
⌊
n
i
⌋
i
)
(
m
−
⌊
m
i
⌋
i
)
\sum_{i=1}^{n} (n-\lfloor \frac{n}{i} \rfloor i) \sum_{i=1}^{m} (m-\lfloor \frac{m}{i} \rfloor i)-\sum_{i=1}^{n} (n-\lfloor \frac{n}{i} \rfloor i) (m-\lfloor \frac{m}{i} \rfloor i)
i=1∑n(n−⌊in⌋i)i=1∑m(m−⌊im⌋i)−i=1∑n(n−⌊in⌋i)(m−⌊im⌋i)
再提
(
n
2
−
∑
i
=
1
n
⌊
n
i
⌋
i
)
(
m
2
−
∑
i
=
1
m
⌊
m
i
⌋
i
)
−
∑
i
=
1
n
(
n
−
⌊
n
i
⌋
i
)
(
m
−
⌊
m
i
⌋
i
)
(n^2-\sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor i)(m^2-\sum_{i=1}^{m} \lfloor \frac{m}{i} \rfloor i)-\sum_{i=1}^{n} (n-\lfloor \frac{n}{i} \rfloor i) (m-\lfloor \frac{m}{i} \rfloor i)
(n2−i=1∑n⌊in⌋i)(m2−i=1∑m⌊im⌋i)−i=1∑n(n−⌊in⌋i)(m−⌊im⌋i)
令
f
i
=
i
2
−
∑
j
=
1
i
⌊
i
j
⌋
i
f_i=i^2-\sum_{j=1}^{i} \lfloor \frac{i}{j} \rfloor i
fi=i2−∑j=1i⌊ji⌋i
f
n
f
m
−
∑
i
=
1
n
(
n
−
⌊
n
i
⌋
i
)
(
m
−
⌊
m
i
⌋
i
)
f_nf_m-\sum_{i=1}^{n} (n-\lfloor \frac{n}{i} \rfloor i) (m-\lfloor \frac{m}{i} \rfloor i)
fnfm−i=1∑n(n−⌊in⌋i)(m−⌊im⌋i)
那么
f
n
f_n
fn 和
f
m
f_m
fm 我们就可以用整除分块来做了
现在还剩 ∑ i = 1 n ( n − ⌊ n i ⌋ i ) ( m − ⌊ m i ⌋ i ) \sum_{i=1}^{n} (n-\lfloor \frac{n}{i} \rfloor i) (m-\lfloor \frac{m}{i} \rfloor i) ∑i=1n(n−⌊in⌋i)(m−⌊im⌋i)
我再拆
∑
i
=
1
n
(
n
m
−
n
⌊
m
i
⌋
i
−
m
⌊
m
i
⌋
i
+
⌊
n
i
⌋
⌊
m
i
⌋
i
2
)
\sum_{i=1}^{n} (nm-n \lfloor \frac{m}{i} \rfloor i - m \lfloor \frac{m}{i} \rfloor i + \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor i^2)
i=1∑n(nm−n⌊im⌋i−m⌊im⌋i+⌊in⌋⌊im⌋i2)
再浅浅提一下
n
2
m
−
∑
i
=
1
n
(
i
(
n
⌊
m
i
⌋
+
m
⌊
n
i
⌋
)
−
⌊
n
i
⌋
⌊
m
i
⌋
i
2
)
n^2m-\sum_{i=1}^{n} ( i(n \lfloor \frac{m}{i} \rfloor +m \lfloor \frac{n}{i} \rfloor)- \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor i^2 )
n2m−i=1∑n(i(n⌊im⌋+m⌊in⌋)−⌊in⌋⌊im⌋i2)
n
2
m
n^2m
n2m 当然不用说啦,直接算就好了。显然
∑
i
=
1
n
i
(
n
⌊
m
i
⌋
+
m
⌊
n
i
⌋
)
\sum_{i=1}^{n} i(n \lfloor \frac{m}{i} \rfloor +m \lfloor \frac{n}{i} \rfloor)
∑i=1ni(n⌊im⌋+m⌊in⌋) 也是整除分块
那就只剩 ∑ i = 1 n ⌊ n i ⌋ ⌊ m i ⌋ i 2 \sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor i^2 ∑i=1n⌊in⌋⌊im⌋i2 了
我们分开来看 ⌊ n i ⌋ ⌊ m i ⌋ \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor ⌊in⌋⌊im⌋ 和 i 2 i^2 i2
首先看 ⌊ n i ⌋ ⌊ m i ⌋ \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor ⌊in⌋⌊im⌋
这是一个二维整除分块
来,上前置知识!!!
前置芝士:二维整除分块
其实好像也没什么😂
我们以本题为例,要求 ∑ i = 1 n ⌊ n i ⌋ ⌊ m i ⌋ \sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor ∑i=1n⌊in⌋⌊im⌋
那么对于原来每块的值 k ,我们现在要求两个 k 1 = ⌊ n i ⌋ k1=\lfloor \frac{n}{i} \rfloor k1=⌊in⌋ 和 k 2 = ⌊ m i ⌋ k2=\lfloor \frac{m}{i} \rfloor k2=⌊im⌋
那么 r = min ( n k 1 , m k 2 ) r=\min(\frac{n}{k1},\frac{m}{k2}) r=min(k1n,k2m)
其他一毛一样(该乘乘,该干嘛干嘛)
回到题目
那么对于 ∑ i = l r i 2 \sum_{i=l}^{r} i^2 ∑i=lri2
我们尝试使用类似前缀和的思路
即 ∑ i = l r i 2 = ∑ i = 1 r i 2 − ∑ i = 1 l − 1 i 2 \sum_{i=l}^{r} i^2=\sum_{i=1}^{r} i^2 - \sum_{i=1}^{l-1} i^2 ∑i=lri2=∑i=1ri2−∑i=1l−1i2
那么怎么求 ∑ i = 1 x i 2 \sum_{i=1}^{x} i^2 ∑i=1xi2 呢
我们可以知道,这等于 x ( x + 1 ) ( 2 x + 1 ) 6 \frac{x(x+1)(2x+1)}{6} 6x(x+1)(2x+1)
至于为什么
看官且看:
我们知道 ( n + 1 ) 3 − 1 3 = ∑ i = 1 n ( ( i + 1 ) 3 − i 3 ) (n+1)^3-1^3=\sum_{i=1}^{n} ((i+1)^3-i^3) (n+1)3−13=∑i=1n((i+1)3−i3)
又知道 ( n + 1 ) 3 = n 3 + 3 n 2 + 3 n + 1 (n+1)^3=n^3+3n^2+3n+1 (n+1)3=n3+3n2+3n+1
把后式套入前式,得
(
n
+
1
)
3
−
1
3
=
∑
i
=
1
n
(
3
i
2
+
3
i
+
1
)
=
3
∑
i
=
1
n
i
2
+
3
∑
i
=
1
n
i
+
∑
i
=
1
n
1
(n+1)^3-1^3=\sum_{i=1}^{n} (3i^2+3i+1)\\ =3\sum_{i=1}^{n} i^2 + 3\sum_{i=1}^{n} i + \sum_{i=1}^{n} 1
(n+1)3−13=i=1∑n(3i2+3i+1)=3i=1∑ni2+3i=1∑ni+i=1∑n1
令
s
n
=
∑
i
=
1
n
i
2
s_n=\sum_{i=1}^{n} i^2
sn=∑i=1ni2
则原式为
(
n
+
1
)
3
−
1
3
=
3
s
n
+
3
(
n
+
1
)
n
2
+
n
(n+1)^3-1^3=3s_n+3 \frac{(n+1)n}{2} +n
(n+1)3−13=3sn+32(n+1)n+n
把
s
n
s_n
sn 移至左边,其他移去右边,得
3
s
n
=
(
n
+
1
)
2
−
1
−
(
n
+
1
)
n
2
−
n
3s_n=(n+1)^2-1- \frac{(n+1)n}{2} - n
3sn=(n+1)2−1−2(n+1)n−n
化简,得
2
(
n
+
1
)
3
−
2
−
3
(
n
+
1
)
n
−
2
n
6
\frac{2(n+1)^3-2-3(n+1)n-2n}{6}
62(n+1)3−2−3(n+1)n−2n
展开,再整理一下,得
2
n
3
+
3
n
2
+
n
6
\frac{2n^3+3n^2+n}{6}
62n3+3n2+n
提出
n
n
n ,得
n
(
2
n
2
+
3
n
+
1
)
6
\frac{n(2n^2+3n+1)}{6}
6n(2n2+3n+1)
记得 ÷ 6 \div6 ÷6 要用逆元,即 × 3323403 \times 3323403 ×3323403 ( 6 6 6 在 % 19940417 \% 19940417 %19940417 意义下的逆元)
那么这道题就是这样了。记得要开 long long 吖
感觉讲得还是有点儿怪(tao
Talk is cheap ,show you the code.
#include<cstdio>
#define ll long long int
#define Md 19940417
#define Inv6 3323403
using namespace std;
ll min1(ll x,ll y)
{
return x<y?x:y;
}
void swp(ll &x,ll &y)
{
ll t=x;
x=y;
y=t;
}
ll sm(ll l,ll r)
{
return ((l+r)*(r-l+1))/2%Md;
}
ll f(ll x)
{
ll l=1,r;
ll s=(x*x)%Md;
while(l<=x)
{
ll k=x/l;
r=x/k;
s=(s-(sm(l,r)*k)%Md+Md)%Md;
l=r+1;
}
return s;
}
ll g(ll x)
{
return (((x*(x+1))%Md*(2*x+1))%Md*Inv6)%Md;
}
int main()
{
ll n,m;
scanf("%lld%lld",&n,&m);
if(n>m)
{
swp(n,m);
}
ll ans=((f(n)*f(m))%Md-((n*n)%Md*m)%Md+Md)%Md;
ll l=1,r;
while(l<=n)
{
ll k1=n/l,k2=m/l;
r=min1(n/k1,m/k2);
ans=((ans+(sm(l,r)*((n*k2)%Md+(m*k1)%Md)%Md))%Md-((g(r)-g(l-1)+Md)%Md*k1%Md*k2)%Md+Md)%Md;
l=r+1;
}
printf("%lld",ans);
return 0;
}
如有错误或遗漏,烦请各位评论区指正。
码字不易(公式打得手软),还请大家多多支持,可以的话点赞关注加收藏哦
谢谢啦🧡
这里默认 n ≤ m n \le m n≤m ↩︎