先讲下整除分块是个啥:要求 ∑ i = 1 n \sum_{i=1}^n ∑i=1nn/i 的值,这时候暴力需要O(n)的时间。由于这个区间是连续的,且’/'是向下取整,当i不能整除k时,n/i会等于最小的i(也就是区间最左边的值 L)除n的商。此时如果可以很快的找到这一个区间,那么就可以将时间复杂度降到O( n \sqrt{n} n)。 接下来讲一下怎么去找这个区间:
如果需要求 ∑ i = 1 20 20 i \sum_{i=1}^{20} \frac{20}{i} ∑i=120i20,把这些要求的样例都写出来找找规律:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
20/i | 20 | 10 | 6 | 5 | 4 | 3 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
看到这个表不难发现规律,用20再去除以 (20/i) 就等于最后一个等于这个值的数,比如说 当i=7时,20/i=2,那么用20/(20/7) = 10, 这个时候10就是20/i等于2的最后一个值。可以利用这个特性,在区间最左边用O(1)的时间就可以计算出区间最右边的坐标。在这个区间内,所有的值都是相同的,所以找到这个区间后,直接用区间长度乘以单个数值就ok。
#include<bits/stdc++.h>
using namespace std;
int n, ans;
int main() {
scanf("%d", &n);
for(int l = 1, r;l <= n; l = r+1) {
r = n/(n/l); // 区间最右边
ans += (n/l) * (r-l+1);
}
printf("%d\n", ans);
}
下一题:
余数求和 要求 ∑ i = 1 n \sum_{i=1}^n ∑i=1nk%i。
∑ i = 1 n \sum_{i=1}^n ∑i=1nk%i
= ∑ i = 1 n \sum_{i=1}^n ∑i=1nk-i*(k/i)*
=n*k - ∑ i = 1 n \sum_{i=1}^n ∑i=1n i * (k/i)
在每一段(L,R)中 k/i = k/L ,所以在相加的时候可以当作公因式提出来。 ∑ i = 1 n \sum_{i=1}^n ∑i=1ni 相当于一个等差数列。由等差数列求和公式可得: (R-L+1) * (L+R) / 2。
所以每一段(L,R)的和可以表示为 k/L * (R-L+1) * (L+R) / 2。
#include <bits/stdc++.h>