求余数之和问题
就相当于一个n和k,将k对1~n的数分别求余数,然后求出来和是多少,其表达公式如j(n,k)=k%1+k%2+k%3+…+k%n。
单单暴力的话,时间上非常浪费,所以这个时候引用数论里面的一些基础知识。用数学思想将这个题简化。
核心公式为:k%i = k - ⌊ k / i ⌋ * i。 (下取整符号)
所有原式就可以写成
所以这个时候只需要看一下 k / i 下取整的结果。在这个地方可以采用打表思想,进行观察。
当n = 20 k = 10 的时候,观察k/i 会发现,会有一块儿一块儿值是相等的,然后如果该值相等的序列就是一个等比数列,可以直接进行求和公式计算,这样会省很多时间,所以只需要找每一块儿的左右边界,遇到的第一个位置可以默认为是左边界,那么右边界怎么求,通过观察会发现,右边界 R = k / (k/i) 。左右边界都有了之后就非常简单了。
只需要遍历一遍,找到每一块儿的左右边界,直接计算出来每一块儿的和,然后记得把 i 直接更新为右边界,因为这一块儿已经计算过了。
但是还有一个问题需要注意,就是 k / i 等于 0 的时候 ,当等于 0 的时候,通过公式可以发现,之后就没必要再进行了,因为后面全是 0 。就可以直接结束了。
基本原理弄明白了就可以看题了。
AcWing199. 余数之和
给出正整数 n和 k,计算 j(n,k)=kmod1+kmod2+kmod3+…+kmodn的值。
例如 j(5,3)=3mod1+3mod2+3mod3+3mod4+3mod5=0+1+0+3+3=7。
输入格式
输入仅一行,包含两个整数 n,k。
输出格式
输出仅一行,即 j(n,k)。
数据范围
1≤n,k≤109
输入样例:
5 3
输出样例:
7
代码:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
int main(void)
{
ll n,k;
cin>>n>>k;
ll sum=n*k; // 通过公式推导出来的,该数为基数,然后减去对应的数就行了。
for(ll i=1;i<=n;i++) // 一共有n个数需要求余累加
{
ll l=i,r,s=k/i;// l 为左边界, r 为右边界, s 为k/i下取整值。
if(k/i==0) break;//如果 为0 直接结束
r=min(n,k/s);//如果右边界在所求的范围之外,则更新一下。
sum-=s*((l+r)*(r-l+1)/2); // sum 减去这一块儿的值。
i=r;//更新 i 值,跳过这一块儿。
}
cout<<sum;//输出结果。
return 0;
}