数论分块总结:
数论分块类似于倍增的思想,可以在某些问题种,将o(n)的时间复杂优化。
(1)首先先来看最简单的分块:
即求
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^n{\lfloor \frac{n}{i} \rfloor}
i=1∑n⌊in⌋
如果没有学数论分块的话,肯定就是暴力枚举了。通过观察我们可以发现:
对于 [n/i] 下取整,某一段连续的值是一样的,显然对于这一段我们可以不用一个一个的算,如果确定了那段的左右边界以及值的话,我们可以通过 (r-l+1) *val 直接算出这一段的和。
怎么确定左右边界和值呢?我们可以以右边界为分割线,化成若干段就行了,然后枚举左边界一段一段的求。
因为对于某一段val值是相同的,假设如果我们知道了 值val,那么右边界就很容易求了,右边界就是最远的嘛,n除以val 的最大值,也就是 [n/val] 下取整。对于val,因为这一段任意一个i, [n/i] 下取整都是相同的,很显然可以用左边界求。代码如下:
for(int l=1;l<=n;l=r+1)
{
r=n/(n/l);
ans+=(r-l+1)*(n/l);
}
(2)对于多关键字数论分块
∑
i
m
i
n
(
N
,
M
)
⌊
N
i
⌋
⌊
M
i
⌋
\sum_i^{min(N,M)}{\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{i}\rfloor}
i∑min(N,M)⌊iN⌋⌊iM⌋
本质思想和普通的数论分块是一样的,即找出相等部分的左右边界,对于双关键字的显然相等部分就是两块相等部分的交集,推广到任意关键字同理。
所以代码如下:
for(int l=1;l<=min(n,m);l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=(n/l)*(m/l)*(r-l+1);
}
(3)对于
∑
i
n
⌊
N
i
⌋
=
∑
i
n
⌊
N
i
⌋
∗
f
(
i
)
\sum_i^n\lfloor \frac{N}{i} \rfloor = \sum_i^n{\lfloor\frac{N}{i}\rfloor*f(i)}
i∑n⌊iN⌋=i∑n⌊iN⌋∗f(i)
显然,如果f[i]=1,就是上面的形式。
对于任意的f(x), 其思想本质还是找相同的部分。假设对于某连续的一段 [n/i] 下取整的值为 val ,那么这一段就是
val* f[l] + val * f[2] + … +val *f [r].显然可以将val 提出来,
即 val * (f[l] + …+ f[r]) 。 一但预处理出f(x)的前缀和,则可表示为 val * ( sum[r] - sum[l-1] )。
代码如下:
//预处理前缀和
for(int l=1;l<=n;l=r+1)
{
r=n/(n/l);
ans+=(n/l)*(sum[r]-sum[l-1]);
}
另外还有数论分块的一些性质
通过打表归纳总结: