# 数论分块
对于包含了向下取整的除法式,例如∑i=1n⌊f(x)n⌋ 这样的算式,直接从1 到n 去枚举计算显然效率是比较低的。而数论分块也就给予了可以快速计算的方法。
我们可以注意到向下取整式有这样的性质:
∀a,b,c∈Z,⌊bca⌋=⌊c⌊ba⌋⌋
证明
向下取整实际上可以写成这样的形式:
a=⌊a⌋+r (0≤r<1)
因此上面式子带入之后可以得到:
⌊bca⌋=⌊c1⋅ba⌋=⌊c1⋅(⌊ba⌋+r)⌋=⌊c1⌊ba⌋+cr⌋=⌊c1⌊ba⌋⌋□
通过上面的性质,我们可以得到下面的结论:
定理(数论分块): 对于常数n 和一个i≤n,使得
⌊in⌋=⌊jn⌋
成立的最大的j(i≤j≤n) 为⌊⌊in⌋n⌋。
证明过程说实话有些偏。
证明
设k=⌊in⌋,有k≤⌊in⌋。
而⌊kn⌋≥⌊⌊in⌋n⌋=⌊i⌋=i,故有j=imax=⌊⌊in⌋n⌋.
这个定理说明了这样的一个问题,例如我们要计算∑i=1nf(x)⌊in⌋ 的时候,如果计算f(x) 前缀和即s(i)=∑j=1if(j) 的时间复杂度并不高,那么我们可以快速的计算这个和式:对于i,我们找到满足⌊in⌋=⌊jn⌋ 的最大的j=⌊⌊in⌋n⌋,那么∑k=inf(x)⌊kn⌋=⌊in⌋⋅(s(j)−s(i))。
以这个题目为例:
给出正整数n 和k,计算:
G(n,k)=i=1∑nkmodi
数据范围为109,在 OI 的时限内使用O(n) 的算法必然是不现实的。
我们考虑到amodb=a−⌊ba⌋⋅b,那么上式可以转换为:
G(n,k)=i=1∑nk−⌊ik⌋⋅i
这样子式子就被转换成了数论分块能够解决的形式。
显然∑i=1nk 是容易计算的,同时函数f(i)=i 的前缀和也非常容易计算,通过数论分块我们能够快速解决这个问题。
不过这道题这边有些不一样,求和的上界是n 而处于向下取整分子上的是k。我们可以加入判断,如果k>n,那么枚举到i>n 的时候自然停止就可以,而如果n>k,那么在i>k 的时候自然停止就可以,因为后面的⌊ik⌋⋅i=0,没有必要计算。故我们循环终止的条件是i>min(n,k)。
这样,这道题就可以解决了!
题解
| def suf_sum(i): |
| |
| return (1 + i) * i // 2 |
| |
| def main(n, k): |
| sum = n * k |
| l = 1 |
| while l <= min(n, k): |
| r = k // (k // l) |
| |
| if r > min(n, k): |
| |
| r = min(n, k) |
| sum -= (suf_sum(r) - suf_sum(l - 1)) * (k // l) |
| l = r + 1 |
| return sum |
| |
| |
| if __name__ == '__main__': |
| n, k = map(int, input().split()) |
| print(main(n, k)) |
# 参考资料
[1] https://oi-wiki.org/math/number-theory/sqrt-decomposition/
[2] https://www.luogu.com.cn/problem/P2261