一、问题引入
试快速计算下式:
Σ
i
=
1
n
f
(
i
)
g
(
⌊
n
i
⌋
)
\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) 计算。
二、分析
首先,我们可以发现,在
i
i
i 从
1
1
1 到
n
n
n 单调递增的过程中,
⌊
n
i
⌋
\lfloor \frac{n}{i} \rfloor
⌊in⌋ 的值从
n
n
n 到
1
1
1 单调递减,但变化次数远远少于
n
n
n 次,因此可以考虑将
⌊
n
i
⌋
\lfloor \frac{n}{i} \rfloor
⌊in⌋ 相同的情况合并,统一计算。更换枚举方式,简化为下式:
Σ
i
=
1
n
f
(
i
)
g
(
⌊
n
i
⌋
)
=
Σ
j
∈
S
(
s
u
m
f
(
r
j
)
−
s
u
m
f
(
l
j
)
)
g
(
j
)
,
S
=
{
⌊
n
i
⌋
∣
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}
经过简化,从 1 1 1 到 n n n 的枚举范围降低到了枚举 S S S 集合。那么 S S S 集合究竟有多大呢?有引理如下。
Lemma. ∀ n ∈ N + , ∣ { ⌊ n i ⌋ ∣ i ∈ N + , i ⩽ n } ∣ ⩽ 2 n , \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∣ 表示集合 S S S 的大小。
简略证明如下:
当
i
i
i 小于
n
\sqrt{n}
n 时,
⌊
n
i
⌋
\lfloor\frac{n}{i}\rfloor
⌊in⌋ 至多有
n
\sqrt{n}
n 种取值。
当
i
i
i 大于
n
\sqrt{n}
n 时,
1
⩽
⌊
n
i
⌋
⩽
n
1\leqslant \lfloor\frac{n}{i}\rfloor \leqslant \sqrt n
1⩽⌊in⌋⩽n ,也至多有
n
\sqrt{n}
n 种取值。
因此,
⌊
n
i
⌋
\lfloor\frac{n}{i}\rfloor
⌊in⌋ 总共至多
2
n
2\sqrt{n}
2n 种取值。
最后,分析一下更换枚举后的复杂度。枚举至多 2 n 2\sqrt{n} 2n 次,单次计算区间和和 g ( x ) g(x) g(x) 函数值均可以在 O ( 1 ) O(1) O(1) 的时间复杂度内解决,因此总复杂度为 O ( n ) O(\sqrt{n}) O(n)。
三、例题和代码
(1)UVA11526
题目大意:
T
T
T 组数据,每组给定一个
n
n
n ,求下式的值。
Σ
i
=
1
n
⌊
n
i
⌋
\Sigma_{i=1}^{n}\lfloor\frac{n}{i}\rfloor
Σi=1n⌊in⌋
解:数论分块板子题。其中 f ( x ) ≡ 1 , g ( x ) = x f(x) \equiv 1,g(x) = x f(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
题目大意:给定
n
n
n 和
k
k
k ,求下式的值。
G
(
n
,
k
)
=
Σ
i
=
1
n
k
%
i
G(n,k) = \Sigma_{i = 1}^{n}{k}\%{i}
G(n,k)=Σi=1nk%i
其中
%
\%
% 为取余运算。
解:转化。
G
(
n
,
k
)
=
Σ
i
=
1
n
k
%
i
=
Σ
i
=
1
n
(
k
−
i
⌊
k
i
⌋
)
=
Σ
i
=
1
n
k
−
Σ
i
=
1
n
i
⌊
k
i
⌋
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}\rfloor
G(n,k)=Σi=1nk%i=Σi=1n(k−i⌊ik⌋)=Σi=1nk−Σi=1ni⌊ik⌋
与数论分块的模板式子很相似,可以确定两个函数分别为
f
(
x
)
=
x
,
g
(
x
)
=
x
f(x) = x, g(x) = x
f(x)=x,g(x)=x 。不过上界不同,分类讨论即可。
对于
k
⩽
n
k\leqslant n
k⩽n的情况,由于
i
⩾
k
i\geqslant k
i⩾k 时
⌊
k
i
⌋
=
0
\lfloor\frac{k}{i}\rfloor = 0
⌊ik⌋=0,没有影响。
对于
k
>
n
k \gt n
k>n 的情况,需要判断区间的右端点
r
r
r 是否超过
n
n
n ,如果超过,则需要修改。代码如下。
#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;
}