数论分块
(一)提出问题
∑
i
=
1
n
⌊
N
i
⌋
\sum_{i=1}^n{\lfloor {N\over i}\rfloor}
∑i=1n⌊iN⌋
暴力?
(二)做法
1.显然可以使用
O
(
N
)
O(N)
O(N)的做法暴力过去,但是数据大了肯定就凉了
2.如果取N=10的话
可以发现
N
/
i
N/i
N/i的值分别是10,5,3,2,2,1,1,1,1,1
这些相同的数字岂不是可以使用区间长度×相同的数字来直接计算吗,然后遍历过程中就能把变量i直接移到下一个位置(具体是哪里后续会解释)
(三)右端点位置的证明
现在给出一个左端点i,那么它的最大右端点 i 1 i_1 i1是多少,可以使得对于任意的 ( i ≤ x ≤ i 1 ) (i\leq x \leq i_1) (i≤x≤i1),都有 ⌊ N x ⌋ = ⌊ N i ⌋ \lfloor {N\over x} \rfloor=\lfloor {N\over i} \rfloor ⌊xN⌋=⌊iN⌋
事实上: i 1 = ⌊ N ⌊ N i ⌋ ⌋ i_1=\lfloor {N\over {\lfloor {N\over i} \rfloor}} \rfloor i1=⌊⌊iN⌋N⌋
证明(显然不是自己会证明的 ):
设
⌊
N
i
⌋
=
k
\lfloor {N\over i }\rfloor=k
⌊iN⌋=k,则根据定义可以写成
N
=
k
i
+
r
,
(
0
≤
r
<
i
)
N=ki+r,(0\leq r<i)
N=ki+r,(0≤r<i)
设
⌊
N
i
+
d
⌋
=
k
\lfloor {N\over {i+d}} \rfloor=k
⌊i+dN⌋=k,同理可得
N
=
k
i
+
k
d
+
r
1
,
(
0
≤
r
1
<
i
)
N=ki+kd+r_1,(0\leq r_1<i)
N=ki+kd+r1,(0≤r1<i)
显然我们只需求出 d m a x = ⌊ r − r 1 k ⌋ = ⌊ r k ⌋ d_{max}=\lfloor {{r-r_1}\over k} \rfloor=\lfloor{r\over k}\rfloor dmax=⌊kr−r1⌋=⌊kr⌋
i 1 = i + d m a x i_1=i+d_{max} i1=i+dmax
= i + ⌊ r k ⌋ =i+\lfloor {r\over k}\rfloor =i+⌊kr⌋
= i + ⌊ N % i ⌊ N i ⌋ ⌋ =i+\lfloor{{N\%i}\over {\lfloor {N\over i }\rfloor}}\rfloor =i+⌊⌊iN⌋N%i⌋
= i + ⌊ N − ⌊ N i ⌋ i ⌊ N i ⌋ ⌋ =i+\lfloor{{N-\lfloor {N\over i}\rfloor}i\over {\lfloor {N\over i }\rfloor}}\rfloor =i+⌊⌊iN⌋N−⌊iN⌋i⌋
= ⌊ i + N − ⌊ N i ⌋ i ⌊ N i ⌋ ⌋ =\lfloor{i+{{N-\lfloor {N\over i}\rfloor}i\over {\lfloor {N\over i }\rfloor}}}\rfloor =⌊i+⌊iN⌋N−⌊iN⌋i⌋
合并同类项后得?
= ⌊ N ⌊ N i ⌋ ⌋ =\lfloor {N\over {\lfloor {N\over i} \rfloor}} \rfloor =⌊⌊iN⌋N⌋
(四)复杂度分析
其实循环的次数就是 ⌊ N i ⌋ \lfloor {N\over i}\rfloor ⌊iN⌋的不同的值的个数,即分块的个数
那么有多少个不同的数,对于一个N,可以分成两种情况考虑:
1.当
i
≤
N
i\leq \sqrt{N}
i≤N的时候,最多也只有i的范围长度即
n
\sqrt{n}
n种可能性
2.当
i
>
N
i>\sqrt{N}
i>N时,
⌊
N
i
⌋
<
n
\lfloor {N\over i}\rfloor <\sqrt{n}
⌊iN⌋<n,最多只有范围长度
n
\sqrt{n}
n种情况
综上,分块个数不会超过 2 n 2\sqrt{n} 2n个,即时间复杂度 O ( n ) O(\sqrt{n}) O(n)
(五)核心代码
ll ans=0;
for (int l=1,r=0;l<=n;l=r+1)
//每次左端点在前一个区间的右端点的下一个位置r+1
{
r=n/(n/l);//右端点
ans+=(r-l+1)*(n/l);
}
(六)例题
1.洛谷 P1403 板子题
题意:求出1-N的每个数的因数总和
做法:实际上并不用打表算,只要算
∑
i
=
1
n
⌊
N
i
⌋
\sum_{i=1}^n{\lfloor {N\over i}\rfloor}
∑i=1n⌊iN⌋,即找出N个数中有多少个数字是含有因数i的,例如7/3=2,所以3和6是含有因数3的,则答案就加上2
int main()
{
ll ans=0;
ll n;
scanf("%lld",&n);
for (ll l=1,r=0;l<=n;l=r+1)
{
r=n/(n/l);
ans+=(r-l+1)*(n/l);
}
WW(ans);
return 0;
}
2.[CQOI2007]余数求和
题意:给出n,k,求
∑
i
=
1
n
(
k
m
o
d
i
)
\sum_{i=1}^n(k \ mod \ i)
∑i=1n(k mod i)
做法:考虑到模数并不能直接套,于是进行如下转化
∑ i = 1 n ( k m o d i ) \sum_{i=1}^n(k \ mod \ i) ∑i=1n(k mod i)
= ∑ i = 1 n ( k − ⌊ k i ⌋ i ) =\sum_{i=1}^n(k-\lfloor {k\over i} \rfloor i) =∑i=1n(k−⌊ik⌋i)
= n k − ∑ i = 1 n i ⌊ k i ⌋ =nk-\sum_{i=1}^ni\lfloor {k\over i} \rfloor =nk−∑i=1ni⌊ik⌋
这就很好做了,将 ⌊ k i ⌋ \lfloor {k\over i} \rfloor ⌊ik⌋的值相同的分为一块,然后 ( l ≤ i ≤ r ) (l\leq i \leq r) (l≤i≤r)就相当于是一个✖上一个定值的等差数列了
for(ll i=1,r;i<=n;i=r+1)
{
if(k/i) r=min(k/(k/i),n);//不判的话会出现除0的错误
else r=n;
ans-=(k/i)*(r-i+1)*(i+r)/2;
}
尚不深究……