一、问题引入
试快速计算下式:
Σi=1nf(i)g(⌊ni⌋) \Sigma_{i=1}^{n}f(i)g(\lfloor \frac{n}{i} \rfloor)Σi=1nf(i)g(⌊in⌋)
其中,f(x)f(x)f(x) 的前缀和已预处理(或可预处理),g(x)g(x)g(x) 可以 O(1)O(1)O(1) 计算。
二、分析
首先,我们可以发现,在 iii 从 111 到 nnn 单调递增的过程中, ⌊ni⌋\lfloor \frac{n}{i} \rfloor⌊in⌋ 的值从 nnn 到 111 单调递减,但变化次数远远少于 nnn 次,因此可以考虑将 ⌊ni⌋\lfloor \frac{n}{i} \rfloor⌊in⌋ 相同的情况合并,统一计算。更换枚举方式,简化为下式:
Σi=1nf(i)g(⌊ni⌋)=Σj∈S(sumf(rj)−sumf(lj))g(j),S={⌊ni⌋∣i∈N+,i⩽n} \Sigma_{i = 1}^{n}f(i)g(\lfloor \frac{n}{i}\rfloor) = \Sigma_{j \in S} (sumf(r_{j}) - sumf(l_{j}))g(j), S = \lbrace\lfloor\frac{n}{i}\rfloor\mid i \in \mathbb{N}_{+}, i \leqslant n \rbraceΣi=1nf(i)g(⌊in⌋)=Σj∈S(sumf(rj)−sumf(lj))g(j),S={⌊in⌋∣i∈N+,i⩽n}
经过简化,从 111 到 nnn 的枚举范围降低到了枚举 SSS 集合。那么 SSS 集合究竟有多大呢?有引理如下。
Lemma. ∀n∈N+,∣{⌊ni⌋∣i∈N+,i⩽n}∣⩽2n,\forall n \in \mathbb{N}_{+}, \left|\left\{\lfloor\frac{n}{i}\rfloor \mid i \in \mathbb{N}_{+},i\leqslant n\right\}\right| \leqslant 2\sqrt{n},∀n∈N+,{⌊in⌋∣i∈N+,i⩽n}⩽2n,∣S∣\left|S\right|∣S∣ 表示集合 SSS 的大小。
简略证明如下:
当 iii 小于 n\sqrt{n}n 时,⌊ni⌋\lfloor\frac{n}{i}\rfloor⌊in⌋ 至多有 n\sqrt{n}n 种取值。
当 iii 大于 n\sqrt{n}n 时,1⩽⌊ni⌋⩽n1\leqslant \lfloor\frac{n}{i}\rfloor \leqslant \sqrt n1⩽⌊in⌋⩽n ,也至多有 n\sqrt{n}n 种取值。
因此,⌊ni⌋\lfloor\frac{n}{i}\rfloor⌊in⌋ 总共至多 2n2\sqrt{n}2n 种取值。
最后,分析一下更换枚举后的复杂度。枚举至多 2n2\sqrt{n}2n 次,单次计算区间和和 g(x)g(x)g(x) 函数值均可以在 O(1)O(1)O(1) 的时间复杂度内解决,因此总复杂度为 O(n)O(\sqrt{n})O(n)。
三、例题和代码
(1)UVA11526
题目大意:TTT 组数据,每组给定一个 nnn ,求下式的值。
Σi=1n⌊ni⌋\Sigma_{i=1}^{n}\lfloor\frac{n}{i}\rfloorΣi=1n⌊in⌋
解:数论分块板子题。其中 f(x)≡1,g(x)=xf(x) \equiv 1,g(x) = xf(x)≡1,g(x)=x。代码如下。
#include<bits/stdc++.h>
using namespace std;
int T, n;
int sumf(int x){
return x;
}
int g(int x){
return x;
}
int main(){
scanf("%d", &T);
while (T --){
scanf("%d", &n);
long long l = 1, r, ans = 0;
while (l <= n){
r = n / (n / l);
ans += (sumf(r) - sumf(l - 1)) * g(n / l);
l = r + 1;
}
printf("%lld\n", ans);
}
return 0;
}
(2) P2261
题目大意:给定 nnn 和 kkk ,求下式的值。
G(n,k)=Σi=1nk%iG(n,k) = \Sigma_{i = 1}^{n}{k}\%{i}G(n,k)=Σi=1nk%i
其中 %\%% 为取余运算。
解:转化。
G(n,k)=Σi=1nk%i=Σi=1n(k−i⌊ki⌋)=Σi=1nk−Σi=1ni⌊ki⌋G(n,k) = \Sigma_{i = 1}^{n}{k}\%{i} = \Sigma_{i = 1}^{n}(k - i\lfloor\frac{k}{i}\rfloor) = \Sigma_{i = 1}^{n}k - \Sigma_{i = 1}^{n}i\lfloor\frac{k}{i}\rfloorG(n,k)=Σi=1nk%i=Σi=1n(k−i⌊ik⌋)=Σi=1nk−Σi=1ni⌊ik⌋
与数论分块的模板式子很相似,可以确定两个函数分别为 f(x)=x,g(x)=xf(x) = x, g(x) = xf(x)=x,g(x)=x 。不过上界不同,分类讨论即可。
对于 k⩽nk\leqslant nk⩽n的情况,由于 i⩾ki\geqslant ki⩾k 时 ⌊ki⌋=0\lfloor\frac{k}{i}\rfloor = 0⌊ik⌋=0,没有影响。
对于 k>nk \gt nk>n 的情况,需要判断区间的右端点 rrr 是否超过 nnn ,如果超过,则需要修改。代码如下。
#include<bits/stdc++.h>
using namespace std;
int n, k;
long long sumf(int x){
return 1ll * x * (x + 1) / 2;
}
long long g(int x){
return x;
}
int main(){
scanf("%d%d", &n, &k);
if (k <= n){
long long l = 1, r, ans = 0;
while (l <= k){
r = k / (k / l);
ans += (sumf(r) - sumf(l - 1)) * g(k / l);
l = r + 1;
}
printf("%lld\n", 1ll * n * k - ans);
}
else {
long long l = 1, r, ans = 0;
while (l <= n){
r = k / (k / l);
if (r > n) r = n;
ans += (sumf(r) - sumf(l - 1)) * g(k / l);
l = r + 1;
}
printf("%lld\n", 1ll * n * k - ans);
}
return 0;
}