题目描述
给出正整数n和k,计算j(n, k)=k mod 1 + k mod 2 + k mod 3 + … + k mod n的值。
例如j(5, 3)=3 mod 1 + 3 mod 2 + 3 mod 3 + 3 mod 4 + 3 mod 5=0+1+0+3+3=7。
输入格式
输入仅一行,包含两个整数n, k。
输出格式
输出仅一行,即j(n, k)。
数据范围
1≤n,k≤1e9
输入样例
5 3
输出样例
7
思路:
这题,纯数学题。
已知
k
m
o
d
i
=
k
−
⌊
k
/
i
⌋
∗
i
k \ mod\ i = k - \lfloor k / i \rfloor * i
k mod i=k−⌊k/i⌋∗i
所以:
∑
i
=
1
n
k
m
o
d
i
=
n
∗
k
−
∑
i
=
1
n
⌊
k
/
i
⌋
∗
i
\sum_{i=1}^n{k\ mod\ i} = n * k - \sum_{i=1}^n{\lfloor k / i \rfloor * i}
i=1∑nk mod i=n∗k−i=1∑n⌊k/i⌋∗i
由此可以看出,我们必须将
∑
i
=
1
n
⌊
k
/
i
⌋
∗
i
{\sum_{i=1}^n\lfloor k / i \rfloor * i}
∑i=1n⌊k/i⌋∗i 进行简化计算。
⌊
k
/
i
⌋
\lfloor k / i \rfloor
⌊k/i⌋ 随着
i
i
i 的递增,是一个递减的趋势,也就是一个单调递减的性质。
对于任意的整数
l
∈
[
1
,
k
]
l\in[1, k]
l∈[1,k],设
r
=
⌊
k
/
⌊
k
/
l
⌋
⌋
r=\lfloor k/\lfloor k/l\rfloor\rfloor
r=⌊k/⌊k/l⌋⌋
又
⌊
k
/
l
⌋
≤
(
k
/
l
)
\lfloor k / l\rfloor\leq(k/l)
⌊k/l⌋≤(k/l)
显然有
r
≥
⌊
k
/
(
k
/
l
)
⌋
=
x
r\geq\lfloor k/(k/l)\rfloor=x
r≥⌊k/(k/l)⌋=x,所以,
⌊
k
/
r
⌋
≤
⌊
k
/
l
⌋
\lfloor k/r\rfloor\leq\lfloor k/l\rfloor
⌊k/r⌋≤⌊k/l⌋。
又因为,
⌊
k
/
r
⌋
≥
⌊
k
/
(
k
/
⌊
k
/
l
⌋
)
⌋
=
⌊
k
/
k
∗
⌊
k
/
l
⌋
⌋
=
⌊
k
/
l
⌋
\lfloor k / r\rfloor\geq\lfloor k/(k/\lfloor k/l\rfloor)\rfloor=\lfloor k/k*\lfloor k/l\rfloor\rfloor=\lfloor k/l\rfloor
⌊k/r⌋≥⌊k/(k/⌊k/l⌋)⌋=⌊k/k∗⌊k/l⌋⌋=⌊k/l⌋
所以:
⌊
k
/
r
⌋
=
⌊
k
/
l
⌋
\lfloor k / r\rfloor=\lfloor k/l\rfloor
⌊k/r⌋=⌊k/l⌋。
并且对于任意的
i
∈
[
l
,
⌊
k
/
⌊
k
/
l
⌋
⌋
]
i\in[l, \lfloor k/\lfloor k/l\rfloor\rfloor]
i∈[l,⌊k/⌊k/l⌋⌋],
⌊
k
/
i
⌋
\lfloor k/i\rfloor
⌊k/i⌋的值都相等,图像为一段平行于x轴的线段。这样,我们就可以将循环的范围进行缩小,优化了效率。
接下来证明一下循环的范围。
∀ i ∈ [ 1 , k ] \forall i\in[1,k] ∀i∈[1,k]
- 当 i ≤ k i\leq\sqrt k i≤k 时, i i i 为 1 ⇒ k 1\Rightarrow \sqrt k 1⇒k 的整数,所以 ⌊ k / i ⌋ \lfloor k/i\rfloor ⌊k/i⌋最多只有 k \sqrt k k个不同的值。
- 当 i > k i>\sqrt k i>k 时, ⌊ k / i ⌋ < k \lfloor k/i\rfloor<\sqrt k ⌊k/i⌋<k,又因为 ⌊ k / i ⌋ \lfloor k/i\rfloor ⌊k/i⌋是向下取整,故 ⌊ k / i ⌋ \lfloor k/i\rfloor ⌊k/i⌋也最多只有 k \sqrt k k 个不同的取值。
所以,
⌊
k
/
i
⌋
\lfloor k/i\rfloor
⌊k/i⌋至多只有
2
k
2\sqrt k
2k 个不同的值。
综上所述,对于
i
=
[
1
,
k
]
i=[1,k]
i=[1,k],
⌊
k
/
i
⌋
\lfloor k/i\rfloor
⌊k/i⌋由不超过
2
k
2\sqrt k
2k不同的值段组成,每一段的值都相等。
所以对于每一段
∑
i
=
l
r
⌊
k
/
i
⌋
∗
i
{\sum_{i=l}^r\lfloor k / i \rfloor * i}
∑i=lr⌊k/i⌋∗i 可以简化成
⌊
k
/
l
⌋
∗
∑
i
=
l
r
i
\lfloor k / l\rfloor*\sum_{i=l}^ri
⌊k/l⌋∗∑i=lri。
而
∑
i
=
l
r
i
\sum_{i=l}^ri
∑i=lri 是一个等差数列求和,代代求和公式就可求出。
算法设计:
- 先让 l = 1 l=1 l=1,再计算出求和的上界 r r r,最后代公式计算整段的值
- 更新每段的段首,即 l = r + 1 l=r+1 l=r+1
- 重复1、2步,直到取完所有的可选段,即当 k / l k/l k/l 为零的时候,上界就取完了。
注意数据范围,可能会炸int,所以索性全开long long 了。
注意一下上界只能取到右端点n,如果大于n,这段值为零是没有贡献的,所以得判断一下
⌊
k
/
⌊
k
/
l
⌋
⌋
\lfloor k /\lfloor k / l\rfloor\rfloor
⌊k/⌊k/l⌋⌋ 与
n
n
n 的大小关系,上界取min
代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL n, k, res;
int main()
{
cin >> n >> k;
res = n * k;
for(int l = 1, r; l <= n; l = r + 1)
{
//判断上界是否为0,如果为0,则说明取到无值贡献的段了,可以直接break掉,也可以让r等于右端点n。
r = k / l ? min(k/(k/l), n) : n;
//(l + r) * (r - l + 1) / 2 是等差数列求和
res -= (k/l) * (l + r) * (r - l + 1) / 2;
}
cout << res << endl;
return 0;
}