一、题目链接
二、题目大意
给定正整数
n
n
n,
k
k
k,要求计算
∑
i
=
1
n
k
m
o
d
i
\sum_{i=1}^{n}k\ mod\ i
∑i=1nk mod i。
数据范围:
1
≤
n
,
k
≤
1
0
9
1\leq n,k\leq 10^9
1≤n,k≤109
三、题目分析
如果直接求解这个问题,它的时间复杂度就是 O ( n ) O(n) O(n),不满足题目的要求,所以我们要首先分析这个运算的本质。
根据取余运算的特性,我们知道 k m o d i = k − ⌊ k / i ⌋ × i k\ mod\ i=k-\lfloor k/i\rfloor \times i k mod i=k−⌊k/i⌋×i,所以,我们要求的东西就变成了 n × k − ∑ i = 1 n ⌊ k / i ⌋ × i n\times k-\sum_{i=1}^{n}\lfloor k/i\rfloor \times i n×k−∑i=1n⌊k/i⌋×i。
我们很容易发现,在当 i i i为某些值的时候, ⌊ k / i ⌋ \lfloor k/i\rfloor ⌊k/i⌋的值是相同的,我们考虑如何将这些相同的数合在一起计算,降低时间复杂度。
- 我们设 t e m p = ⌊ k / ⌊ k / x ⌋ ⌋ temp=\lfloor k/ \lfloor k/x\rfloor\rfloor temp=⌊k/⌊k/x⌋⌋, t e m p ≥ x temp\geq x temp≥x,所以 ⌊ k / t e m p ⌋ ≤ ⌊ k / x ⌋ \lfloor k/temp\rfloor \leq \lfloor k/x\rfloor ⌊k/temp⌋≤⌊k/x⌋
- 又因为 t e m p ≤ k / ⌊ k / x ⌋ = a n o temp\leq k/\lfloor k/x\rfloor=ano temp≤k/⌊k/x⌋=ano,所以 ⌊ k / t e m p ⌋ ≥ ⌊ k / a n o ⌋ \lfloor k/temp\rfloor \geq \lfloor k/ano\rfloor ⌊k/temp⌋≥⌊k/ano⌋
- 显然, k / a n o = ⌊ k / x ⌋ k/ano=\lfloor k/x\rfloor k/ano=⌊k/x⌋。即 ⌊ k / t e m p ⌋ ≥ ⌊ k / x ⌋ \lfloor k/temp\rfloor \geq \lfloor k/x\rfloor ⌊k/temp⌋≥⌊k/x⌋
所以我们知道 ⌊ k / t e m p ⌋ = ⌊ k / x ⌋ \lfloor k/temp\rfloor = \lfloor k/x\rfloor ⌊k/temp⌋=⌊k/x⌋。即在 i ∈ [ x , ⌊ k / ⌊ k / x ⌋ ⌋ ] i\in[x,\lfloor k/ \lfloor k/x\rfloor\rfloor] i∈[x,⌊k/⌊k/x⌋⌋]时 ⌊ k / i ⌋ \lfloor k/i\rfloor ⌊k/i⌋相等。
- 于是我们发现,在同一个区间,即 ⌊ k / x ⌋ \lfloor k/x\rfloor ⌊k/x⌋不变的时候。当 i i i变化时, ⌊ k / x ⌋ ∗ i \lfloor k/x\rfloor*i ⌊k/x⌋∗i就形成了一个等差数列,我们可以直接用等差数列求和公式来计算这一段的答案,时间复杂度为 O ( 1 ) O(1) O(1)。
- 接下来考虑有多少个区间需要计算
- 当 i < k i<\sqrt{k} i<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 \sqrt{k} k种。
综上所述,区间个数的数量级在 k \sqrt{k} k,所以时间复杂度为 O ( k ) O(\sqrt{k}) O(k)。
四、正解程序
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
int main()
{
ll n,k,ans,gx;
scanf("%lld%lld",&n,&k);
ans=n*k;
if(k==0)
{
printf("%lld\n",ans);
return 0;
}
for(ll x=1;x<=n;x=gx+1)
{
gx=k/x?min(k/(k/x),n):n;
ans-=(k/x)*(x+gx)*(gx-x+1)/2;
}
printf("%lld\n",ans);
return 0;
}