数论分块(整除分块)
证明+推导
模板
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ll n;
cin>>n;
ll ans=0;
/*初始化l为1,因为我们从1开始遍历*/
for(ll l=1,r;l<=n;l=r+1)//下一个值的l就等于r+1,这个不难理解
{
r=n/(n/l);//我们找到最大的r,使得n/r==n/l
ans+=(r-l+1)*(n/l);//接着我们加上求和的值
}
cout<<ans<<'\n';
return 0;
}
例题分析
下面我们来一道稍微复杂一点的题目
C. Floor and Mod
题目大意:给你一个数给你x,y,让你找出有多少对(a,b)满足,
⌊
a
b
⌋
=
\left \lfloor \frac{a}{b}\right \rfloor=
⌊ba⌋=a mod b,其中
1
<
=
a
<
=
x
,
1
<
=
b
<
=
y
1<=a<=x,1<=b<=y
1<=a<=x,1<=b<=y.
等式右边,a mod b = a-
⌊
a
b
⌋
∗
b
\left \lfloor \frac{a}{b}\right \rfloor*b
⌊ba⌋∗b
->
⌊
a
b
⌋
=
a
−
⌊
a
b
⌋
∗
b
\left \lfloor \frac{a}{b}\right \rfloor=a-\left \lfloor \frac{a}{b}\right \rfloor*b
⌊ba⌋=a−⌊ba⌋∗b。
->
⌊
a
b
⌋
∗
(
b
+
1
)
=
a
\left \lfloor \frac{a}{b}\right \rfloor*(b+1)=a
⌊ba⌋∗(b+1)=a
->
⌊
a
b
⌋
=
a
b
+
1
\left \lfloor \frac{a}{b}\right \rfloor=\frac{a}{b+1}
⌊ba⌋=b+1a
我们令k=
⌊
a
b
⌋
\left \lfloor \frac{a}{b}\right \rfloor
⌊ba⌋,k =
a
b
+
1
\frac{a}{b+1}
b+1a
因为k = a mod b,所以
k
∈
[
0
,
b
−
1
]
k\in[0,b-1]
k∈[0,b−1]
所以
a
=
k
∗
(
b
+
1
)
,
a
∈
[
0
,
b
2
−
1
]
a=k*(b+1),a\in[0,b^{2}-1]
a=k∗(b+1),a∈[0,b2−1]
问题就变成了
∑
b
=
1
y
m
i
n
(
x
,
b
2
−
1
)
b
+
1
\sum_{b=1}^{y}\frac{min(x,b^2-1)}{b+1}
∑b=1yb+1min(x,b2−1),如果枚举b的话显然会超时
所以分块求和
①第一部分:当
b
2
−
1
<
=
x
,
b
<
=
x
+
1
,
此
时
b
2
−
1
b
+
1
=
b
−
1
b^2-1<=x,b<=\sqrt{x+1},此时\frac{b^2-1}{b+1}=b-1
b2−1<=x,b<=x+1,此时b+1b2−1=b−1,此时
m
i
n
(
x
,
b
2
−
1
)
min(x,b^2-1)
min(x,b2−1)就是
b
2
−
1
b^2-1
b2−1,令
t
=
x
+
1
t=\sqrt{x+1}
t=x+1我们对第一部分求和得到,
∑
b
=
1
m
i
n
(
y
,
t
)
(
b
−
1
)
\sum_{b=1}^{min(y,t)}(b-1)
∑b=1min(y,t)(b−1),这就是一个等差数列求和,时间复杂度
O
1
O1
O1。
②第二部分我们,我们在求和第二部分之前先要特判一下有没有第二部分,如果第一部分的t>=y,那么就没有第二部分了,如果t<y,那么我们继续求第二部分, ∑ b = t + 1 y m i n ( x , b 2 − 1 ) b + 1 \sum_{b=t+1}^{y}\frac{min(x,b^2-1)}{b+1} ∑b=t+1yb+1min(x,b2−1),此时 m i n ( x , b 2 − 1 ) min(x,b^2-1) min(x,b2−1)就是x了, ∑ b = t + 1 y x b + 1 \sum_{b=t+1}^{y}\frac{x}{b+1} ∑b=t+1yb+1x,这里的求和也不要暴力遍历b,直接用我们上面的数论整除分块求和模板即可复杂度是 O n O\sqrt{n} On
最后我们将两部分结果加起来就得到了答案
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ll T;
cin>>T;
while(T--)
{
ll ans=0;
ll x,y;
cin>>x>>y;
ll t = sqrt(x+1);//我们先找到第一部分和第二部分的分界点t
if(t>y)//我们这里判断有没有第二部分,如果只有第一部分就只求和第一部分,这里是没有第二部分的情况
{
t=y;
ans+=(t-1)*t/2;//这里是等差数列求和,套公式即可
}
/*这里是有第二部分的情况*/
else{
ans+=(t-1)*t/2;//我们对第一部分求和
/*这里我们就用数论的整除分块来求取第二部分*/
for(ll l=t+1,r;l<=min(x,y);l=r+1)
{
if (x / (l + 1) == 0||l>y) break;//如果出现除式结果为0,或者l的值超过了y就结束循环
r = min(y, min(x, x / (x / (l + 1)) - 1));//注意我们这里r不能超过x,y的最小值
ans+=(r-l+1)*(x/(l+1));
}
}
cout<<ans<<'\n';
}
return 0;
}