洛谷 P2261:[CQOI2007] 余数求和 ← 整数分块算法
【题目来源】
【题目描述】
给出正整数 n 和 k,请计算 G(n,k)=∑(k mod i),其中,i=1~n。
其中,k mod i 表示 k 除以 i 的余数。
【输入格式】
输入只有一行两个整数,分别表示 n 和 k。
【输出格式】
输出一行一个整数表示答案。
【输入样例】
10 5
【输出样例】
29
【数据范围】
对于 30% 的数据,保证 n,k≤10^3。
对于 60% 的数据,保证 n,k≤10^6。
对于 100% 的数据,保证 1≤n,k≤10^9。
【算法分析】
● 样例解释:G(10,5)=5%1+5%2+5%3+5%4+5%5+5%6+5%7+5%8+5%9+5%10=0+1+2+1+0+5+5+5+5+5=29
● “序列分块”算法的基本要素及 build() 函数的构建细节:
学术界通常把分块归类为“根号算法”或“数据结构的平摊优化”。
● 本题“整数分块”算法思想
(1)题目要求:G(n,k)=∑(k mod i),其中,i=1~n。
利用余数公式 k mod i=k−i×⌊k/i⌋,可得:G(n,k)=∑(k mod i)=∑(k−i×⌊k/i⌋)=n×k−∑i×⌊k/i⌋,其中,i=1~n。
所以关键在于快速计算 S=∑i×⌊k/i⌋,其中,i=1~n。
(2)核心观察:⌊k/i⌋ 的取值是分段的。
举一个具体例子,设 k=25:
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | … | 25 | 26 | … |
| ⌊25/i⌋ | 25 | 12 | 8 | 6 | 5 | 4 | 3 | 3 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | … | 1 | 0 | … |
可以看到:随着 i 增大,⌊k/i⌋ 的值单调递减,但不是均匀变化的。同一个值会出现在连续的一段区间内。
核心思想:既然在一段区间 [le, ri] 内 ⌊k/i⌋ 的值都等于同一个常数 t,那我们就可以把这段区间打包计算,而不必一个一个算。
(3)如何确定每个区间的左右端点?
假设当前区间左端点为 le,当前块的值为 t=⌊k/le⌋。
那么右端点 ri 应该是满足 ⌊k/i⌋=t 的最大 i,也就是:ri=max{i∣⌊k/i⌋=t}。
这个最大值可以通过下面的公式直接算出:ri=⌊k/t⌋。
因为当 i>k/t 时,⌊k/i⌋ 会小于 t。
同时还要考虑 n 的限制,所以实际右端点是:ri=min(n, ⌊k/t⌋)。
(4)如何计算每个区间的贡献?
在区间 [le,ri] 内,⌊k/i⌋=t(常数),所以这部分对 S 的贡献为:
∑i×⌊k/i⌋=∑i×t=t×∑i=t×(ri+le)(ri−le+1)/2,其中,i=le ~ ri。
这是一个等差数列求和,可以直接用公式 O(1) 算出。
【算法代码】
【参考文献】

浙公网安备 33010602011771号