一看就知道是一个数学题。嘿嘿~
讲讲各种分的做法吧。
30分做法:不知道,这大概是这题的难点吧!
60分做法:
一是直接暴力,看下代码吧~
#include <bits/stdc++.h>
using namespace std;
typedef int _int;
#define int long long
_int main()
{
int n,k,ans=0;
cin>>n>>k;
for (int i=1;i<=n;++i) {
ans+=(k%i);
}
cout<<ans;
return 0;
}
第二种做法非常接近正解。
首先显然(k~mod~i=k-lfloor frac{k}{i} floor*i)。
所以我们马上一波转化,(sum_{i=1}^{n}k~mod~i=n*k-sum_{i=1}^{n}lfloor frac{k}{i} floor*i)。
那么这一截(sum_{i=1}^{n}lfloor frac{k}{i} floor*i)怎么求呢?
这个时候,直觉会告诉我们,(lfloor frac{k}{i} floor*i)很有问题。
因为是向下取整,所以会有许多(lfloor frac{k}{i} floor)是一样的。于是就会有一个一个的区间。
对于每个这样的区间,在乘一个(i)后,显然是一个等差数列。
不信看这个:
((int)8/3=(int)8/4=2~~~~=>~~~~8/3*3+2=8/4*4)
所以我们可以枚举(i),对于每一个(i),求出(t=k/i),
令(l=i,r=min(n,k))二分,如果(mid/i=t,l)扩大,否则(r)缩小。
找到后直接等差数列求和。
最后使(i=r+1)。这样表面时间复杂度是(O(sqrt{n}*log(n)))。
实则不然,因为我们的(i)跳跃的距离基本上很小很小,所以这代码比(O(n))还慢!
看下代码吧!
#include <bits/stdc++.h>
using namespace std;
typedef int _int;
#define int long long
int n,k,ans;
_int main()
{
cin>>n>>k;
ans=n*k;
for (int i=1;i<=min(n,k);++i) {
int l=i,r=min(n,k),t=k/i,j=i;
while (l<=r) {
int mid=(l+r)/2;
if (mid/i==t) l=mid+1,j=mid;
else r=mid-1;
}
int a1=t*i,an=a1+(j-i)*t,g=j-i+1;
ans-=(g*(a1+an)/2);
i=j;
}
cout<<ans;
return 0;
}
100正解:
有了上面第二个60分做法的思路,正解就不言而喻了。
只要把(log(n))找区间改成(O(1))就好了。
具体怎么改呢?
我们同样的枚举(i),假设区间为([l,r]),那么(l=i)显然,然后就剩(r)有点难搞了。
想想,我们每一段的公差都是(lfloor frac{k}{i} floor),那么显然当(k~mod~i=0)时,(r)截止。
所以,(r=k/(k/l))。
那么,就完结了,上代码!真正的极简AC难懂~
#include <bits/stdc++.h>
using namespace std;
typedef int _int;
#define int long long
int n,k,ans;
_int main()
{
cin>>n>>k;
ans=n*k;
for (int i=1;i<n;++i) {
int l=i,t=k/l,r=t?min(n,k/t):n;
int a1=t*l,an=a1+(r-l)*t,g=r-l+1;
ans-=(g*(a1+an)/2);
i=r;
}
cout<<ans;
return 0;
}