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);
}