洛谷 P2261:[CQOI2007] 余数求和 ← 整数分块算法

【题目来源】
https://www.luogu.com.cn/problem/P2261

【题目描述】
给出正整数 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() 函数的构建细节:https://blog.csdn.net/hnjzsyjyj/article/details/138955263
学术界通常把分块归类为“根号算法”或“数据结构的平摊优化”。

● 本题“整数分块”算法思想
(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) 算出。

【算法代码】

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

int main() {
    LL n,k;
    cin>>n>>k;
    LL ans=n*k;
    for(LL le=1,ri=0; le<=n; le=ri+1) {
        if(le>k) break;
        LL t=k/le;
        ri=min(n,k/t);
        LL sum=(le+ri)*(ri-le+1)/2;
        ans-=t*sum;
    }
    cout<<ans<<endl;

    return 0;
}

/*
in:10 5
out:29
*/



【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/138955263
https://blog.csdn.net/hnjzsyjyj/article/details/138903837
https://blog.csdn.net/hnjzsyjyj/article/details/138863063

posted @ 2026-07-15 20:41  Triwa  阅读(17)  评论(0)    收藏  举报