原题链接:199. 余数之和 - AcWing题库
解题思路:本题题面给出的计算方式可以等价为n次k-(k/i)*i,i属于[1,n](即1到n的闭区间)。因为整型做除法时自动向0取整,再正整数范围内表现为向下取整,所以就是k%i等价于k减去k能被i整除的部分。那么题面的计算结果可以等价为以下公式:
接着设g(x)为:
由以下推论可得向下取整时k/g(x)=k/x(论证部分不看也行,只要先记住这个结论)。
这是结论1
这是结论2
由结论1与结论2可得
由上述证明可以得到当i属于x到g(x)的闭区间内,k/i可以等价为k/x(基于整型运算的向下取整)。那么k/i*i=k/x*i,在x到g(x)的闭区间内,k/x是一个定值,而i每次固定增加1,那么在x到g(x)的闭区间内k/i*i的和可以用等差数列求和来计算。所用的等差数列求和公式为Sn=n*(a1+an)/2。
在题目中,n项为g(x)-x+1(因为是闭区间,不加一的话就变成了x到g(x)半开半闭区间内整数数量了)。而(a1+an)为(k/x*x+k/x*g(x)),因为k/x是两项相同的因数,所以可以把k/x提取到括号外面,变成(k/x)*(x+g(x))。
接下来只需要把n*k减去从x=1开始的x到g(x)的每一段和就能得到答案。即第一段为1到g(1),第二段为g(1)+1到g(g(1)+1),以此类推。
AC代码:
#include<bits/stdc++.h>
using namespace std;
long long n,k,ans;//n与k为题目原意,ans为答案
int main(){
cin>>n>>k;
ans=n*k;//先预设n*k,之后减去被除掉的部分
for(long long x=1,gx;x<=n&&x<=k;x=gx+1){//因为将k划分成数段,每一段都是闭区间[x,g(x)],所以后一段的起始点为前一段的g(x)+1;
//初始值为1是因为从1开始,而x<=n是因为只需要计算到最后一项k%n就截止了,后面不需要计算。
//而小于k是因为大于k的数余k,答案一定是k,不需要减去任何值
gx=min(k/(k/x),n);//如果按照g(x)公式计算超过n则右端点记到n,因为n之后的不需要计算
ans-=(k/x)*(x+gx)*(gx-x+1)/2;//等差数列前n项和公式
}
cout<<ans;
return 0;
}