今天研究了下这玩意,查了几篇 blog,感觉脑子里乱七八糟的,于是自己写篇 blog 尝试总结一下。
前面的几个例子摘自这里,但是式子是自己手推的。
整除分块
整除分块能干啥?
考虑形如 ∑i=1n⌊in⌋ 这样的式子。
但是,使用整除分块能让计算这个东西的速度提升至 O(n)。
考虑 n=10 的情况:
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
⌊in⌋ |
10 |
5 |
3 |
2 |
2 |
1 |
1 |
1 |
1 |
1 |
诶,怎么有这么多重复的 ⌊in⌋?
事实上,随着 i 的增长,这种重复项会越来越多。
于是我们只需要快速统计每种项出现了几次即可。
我们对于每个 ⌊in⌋ 重复的段分开考虑。
n=10 的情况中,这些段分别是 [1,1],[2,2],[3,3],[4,5],[6,10]。
我们可以扫一遍这个段的左端点,然后通过某种神奇方式快速知道这个段的右端点,扫下一个段的时候直接将左端点赋值为这个段的右端点 +1 即可。
那么怎么知道左端点为 l 的区间的右端点 r 是什么呢?
根据我们定义的这种区间的性质:
∀i∈[l,r],⌊in⌋=⌊ln⌋
也就是说,r 是最大的使 ⌊in⌋=⌊ln⌋ 的数 i,即
r=max{i,⌊in⌋=⌊ln⌋}
因此
r=⌊⌊ln⌋n⌋
(这个可以手玩几组样例试试)
也就是说,我们知道 l 之后就能快速知道对应的 r 进行求解。
代码:
1 2 3 4
| for(int l=1,r;l<=n;l=r+1) { r=n/(n/l); ans+=(r-l+1)*(n/l); }
|
然后我们考虑更加一般性的问题,例如求
i=1∑n⌊ai+bn⌋
其中 n,a,b 为给定常数。
我们还是按照老套路,考虑枚举一个 l,快速求解满足 ⌊ai+bn⌋=⌊al+bn⌋ 的最大的 i,即为 r。
这个 r 怎么求呢?
根据上一个例子,我们有
ar+b=⌊⌊al+bn⌋n⌋
因此
r=⎣⎢⎢⎢⎢⎢⎢a⌊⌊al+bn⌋n⌋−b⎦⎥⎥⎥⎥⎥⎥
再考虑一个问题,求
i=1∑n⌊i2n⌋
那么知道 l 的情况下,可以得出
r2=⌊⌊l2n⌋n⌋
因此,
r=⎣⎢⎢⎢⎢⌊⌊l2n⌋n⌋⎦⎥⎥⎥⎥
例题
[CQOI2007] 余数求和
先放一个 link。
G(n,k)=i=1∑nkmodi=i=1∑n(k−i⌊ik⌋)=nk−i=1∑ni⌊ik⌋
因此,我们只需求
i=1∑ni⌊ik⌋
简单推一推,对于区间 [l,r],答案是
⌊lk⌋⋅2(l+r)(r−l+1)
代码:
1 2 3 4 5 6
| int ans=n*k; for(int l=1,r;l<=n;l=r+1) { if(k/l) r=min(k/(k/l),n); else r=n; ans-=(l+r)*(r-l+1)/2*(k/l); }
|
其中,需要注意的细节有
- k 值可能 <n,故可能存在有一个区间答案为 0 的情况。
- 计算出的 r 值可能 >n,故需要与 n 取 min。