CodeForces 616E Sum of Remainders
题目
题目大意
给定 n,m n , m 求 ∑mi=1(nmodi) ∑ i = 1 m ( n mod i ) 。
思路
暴力??? 1013 10 13 ,早就炸了。。。
我们就用数学方法分析一下:
Step1.变模为求和
众所周知, nmodi n mod i 是与 n−i⋅⌊ni⌋ n − i ⋅ ⌊ n i ⌋ 等价的,不信可以找几个数算一算。
记 S=∑mi=1(nmodi) S = ∑ i = 1 m ( n mod i ) ,不难推出 S=n⋅m−∑mi=1i⋅⌊ni⌋ S = n ⋅ m − ∑ i = 1 m i ⋅ ⌊ n i ⌋ 。所以,我们成功地将模运算转化为了求和运算。
Step2. 1013 10 13 ?
n⋅m n ⋅ m 的计算对于我们来说是极其容易的,难点就是后面的那个 ∑mi=1i⋅⌊ni⌋ ∑ i = 1 m i ⋅ ⌊ n i ⌋ 。
作为一个在数学世界里搞了N年事情的大佬,经验能够告诉我:
⌊ni⌋
⌊
n
i
⌋
的结果有些是一样的,于是区间
[1,m]
[
1
,
m
]
就被分成了许多个具有不同
⌊ni⌋
⌊
n
i
⌋
的值所构成的区间。
不明白?举个例子:令
n=18
n
=
18
很容易发现,区间
[1,18]
[
1
,
18
]
被分为了
[1,1],[2,2],[3,3],[4,4],[5,6],[7,9],[10,18]
[
1
,
1
]
,
[
2
,
2
]
,
[
3
,
3
]
,
[
4
,
4
]
,
[
5
,
6
]
,
[
7
,
9
]
,
[
10
,
18
]
七个区间,而这七个区间都可以使用等差数列求出和。
Step3.计算区间??
我们容易发现,每个区间的开始就是上一个区间的结束+1 (废话)。
我们设区间 i i 的开头为,结尾为 ri r i ,不难得出递推式:
边界条件就是 l1=1,r1=1 l 1 = 1 , r 1 = 1 。
Step4.计算答案
终于到了激动人心的时刻了!
我们由上面三步可得,区间 [li,ri] [ l i , r i ] 是一个等差数列,所以对于这个区间,答案应减去:
其中 xi=ni x i = n i 。
实现细节
注意应边模边计算。
正解代码
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int Mod=1e9+7;
ll N,M;
ll GetSum(ll x) {//等差数列
return (x%Mod)*((x+1)%Mod)/2%Mod;
}
ll Sum(ll l,ll r) {
return GetSum(j)-GetSum(i-1);
}
ll ModAdd(ll x,ll y) {
return (x%Mod+y%Mod)%Mod;
}
void ModSub(ll &ans,ll i,ll j,ll x) {
ans=ModAdd(ans,Mod-Sum(j,i-1)*x%Mod);
}
int main() {
#ifdef LOACL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%lld %lld",&N,&M);
ll ans=(N%Mod)*(M%Mod)%Mod;
M=min(N,M);
//当N>i时,x=0,不需要再算
for(ll i=1,j,x;i<=M;i=j+1) {
x=N/i;
j=min(N/max(x,1*1LL),M);
ModSub(ans,i,j,x);
}
printf("%lld\n",ans);
return 0;
}