题目链接
题目描述
对于给定的正整数 ,求表达式
的值。
解题思路
本题是 整除分块(也称 数论分块)算法的模板题。
由于 的范围可以达到
,使用
的朴素循环来计算每一项的和是不可行的,会导致超时。我们需要一个更高效的算法。
核心观察
我们注意到,在表达式 中,随着
的增加,商的值并不是连续变化的,而是呈阶梯状下降。这意味着有许多连续的
会得到完全相同的
值。
例如,当 时:
我们可以把具有相同商值的连续区间 看作一个“块”,对每个块进行整体计算,从而大大减少计算量。
块的边界
算法的关键在于,对于一个块的左边界 ,如何快速找到它的右边界
。
假设当前块内所有 的商都等于
。我们想找到最大的
使得
。
这个右边界可以通过一个简单的公式得出:。
算法流程
-
初始化答案
ans = 0
,左边界l = 1
。 -
当
l <= N
时,循环执行:a. 计算当前块的商
v = N / l
。b. 计算当前块的右边界
r = N / v
。c. 这个块包含的项数为
(r - l + 1)
,每一项的值都是v
。将贡献(r - l + 1) * v
累加到ans
中。d. 将左边界
l
更新为r + 1
,跳到下一个块的起始位置。 -
循环结束后,
ans
即为所求答案。
可以证明, 的不同取值最多只有
个,因此该算法的时间复杂度为
,足以通过本题。
代码
#include <iostream>
using namespace std;
int main() {
long long n;
cin >> n;
long long ans = 0;
for (long long l = 1, r; l <= n; l = r + 1) {
long long v = n / l;
r = n / v;
ans += (r - l + 1) * v;
}
cout << ans << "\n";
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
long ans = 0;
for (long l = 1, r; l <= n; l = r + 1) {
long v = n / l;
r = n / v;
ans += (r - l + 1) * v;
}
System.out.println(ans);
}
}
def solve():
n = int(input())
ans = 0
l = 1
while l <= n:
v = n // l
r = n // v
ans += (r - l + 1) * v
l = r + 1
print(ans)
solve()
算法及复杂度
- 算法:整除分块 (Integer Division Block)
- 时间复杂度:
。
- 空间复杂度:
。