G
求n模i的余数的前k大的和,其中i属于。
首先是一个和整除,求和有关的东西,先想想数论分块。实际上
n模i=n-floor(n/i)*i
然后在整除分块的一个块里是一样的,所以在一个块里,余数是一个等差数列。然后我们要求前k大的余数的和,可以找到第k大的的余数的值,然后对于大于这个值的余数,直接求和,对于等于这个值的余数,取一定个数的求和,直到求和元素总个数等于k
那么我们首先需要找到第k大余数的值,这可以二分第k大实现,我们每次check就遍历一遍所有块,把大于mid的值的个数求和,如果总数小于,就缩小mid,反之增大mid。计算一个块大于mid的余数个数时,我们可以先算出这个等差数列的首项和公差,然后可以计算小于等于mid的元素有多少个,总数减去小于等于的个数,就是大于的个数。(当然也可以直接算大于的个数,我是因为一开始写的就是小于就按这个写了)
然后找到第k大值之后,再遍历一遍所有块,把每个块里大于该值的元素求和,这里也采用和前面类似的思路,大于该值的元素的和=元素总和-小于等于该值的元素的和,并计算选择了多少个元素。
最后看选择的元素个数比k小多少,剩余到k的元素值肯定都是第k大的元素的值,用第k大元素补齐
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,k;
cin>>n>>k;
int l=0,r=n;
auto check=[&](int m)->bool{
int l=1,r;
int sum=0;
while(l<=n){
r=n/(n/l);
int x=n%r;
int y=n/r;
sum+=r-l+1-min((m-x+y)/y,r-l+1);
l=r+1;
}
return sum<k;
};
while(l<=r){
int m=l+r>>1;
if(check(m))r=m-1;
else l=m+1;
}
int val=l;
auto cal=[&](long long x,int y,long long n)->long long{
return (x+x+(n-1)*y)*n/2;
};
l=1;
long long ans=0;
int cnt=0;
while(l<=n){
r=n/(n/l);
int x=n%r;
int y=n/r;
int t=(val-x+y)/y;
cnt+=r-l+1-min((val-x+y)/y,r-l+1);
//cout<<l<<' '<<r<<' '<<val<<' '<<t<<'\n';
ans+=cal(x,y,r-l+1)-cal(x,y,min(r-l+1,t));
l=r+1;
}
cout<<ans+1ll*val*(k-cnt);
}