余数求和 洛谷p2261

来源:互联网 发布:数据的统计 编辑:程序博客网 时间:2024/05/19 16:32

题目描述

给出正整数n和k,计算G(n, k)=k mod 1 + k mod 2 + k mod 3 + … + k mod n的值,其中k mod i表示k除以i的余数。例如G(10, 5)=5 mod 1 + 5 mod 2 + 5 mod 3 + 5 mod 4 + 5 mod 5 …… + 5 mod 10=0+1+2+1+0+5+5+5+5+5=29

输入输出格式

输入格式:

两个整数n k

输出格式:

答案

输入输出样例

输入样例#1: 复制
10 5
输出样例#1: 复制
29

说明

30%: n,k <= 1000

60%: n,k <= 10^6

100% n,k <= 10^9


  • 根据题目可以写出ans=\sum\limits_{i=1}^{n}k\%ians=i=1nk%i

  • 首先知道一点 a\%ba%b 可以表示为 a-b*\lfloor\frac{a}{b}\rfloorabba,写过高精取模的人应该都知道

  • 所以 ans=\sum\limits_{i=1}^{n}k-i*\lfloor\frac{k}{i}\rfloor=n*k-\sum\limits_{i=1}^{n}i*\lfloor\frac{k}{i}\rfloorans=i=1nkiik=nki=1niik

  • 然后 \lfloor\frac{k}{i}\rfloorik 可以出发分块来做,\lfloor\frac{k}{i}\rfloorik大约有\sqrt kk种取值,所以时间复杂度


  • O(\sqrt k)O(k)

我实在是太弱,只好去刷水题了。

首先注意到,对于任意一个$1≤i≤k$,\lfloor\frac{k}{i}\rfloorik的取值只有O(\sqrt k)O(k)种,并且相同的\lfloor\frac{k}{i}\rfloorik的取值对应的ii都是连续的一段区间。所以,先把所有满足条件的区间提取出来。

对于任意一个\lfloor\frac{k}{i}\rfloor==\lfloor\frac{k}{i+1}\rfloorik==i+1k,有:

k=i*\lfloor\frac{k}{i}\rfloor+k \mod i=(i+1)*\lfloor\frac{k}{i+1}\rfloor+k \mod (i+1)k=iik+kmodi=(i+1)i+1k+kmod(i+1)

x=\lfloor\frac{k}{i}\rfloor,a=k \mod ix=ik,a=kmodi,则有:

i*x+a=(i+1)*x+k \mod (i+1)ix+a=(i+1)x+kmod(i+1)

化简得a=x+k \mod (i+1)a=x+kmod(i+1)

即对于任意一个\lfloor\frac{k}{i}\rfloor==\lfloor\frac{k}{i+1}\rfloorik==i+1k,有k \mod i=k \mod (i+1)+\lfloor\frac{k}{i}\rfloorkmodi=kmod(i+1)+ik

也就是说,对于一段区间[l,r][l,r],如果\lfloor\frac{k}{l}\rfloor,\lfloor\frac{k}{l+1}\rfloor,...,\lfloor\frac{k}{r-1}\rfloor,\lfloor\frac{k}{r}\rfloorlk,l+1k,...,r1k,rk的值全部相同,则有:

\sum_{i=l}^rk\mod i=(k \mod l)*(r-l+1)-\frac{\lfloor\frac{k}{l}\rfloor*(r-l)*(r-l+1)}{2}i=lrkmodi=(kmodl)(rl+1)2lk(rl)(rl+1)

这样就很好做了。具体实现见代码。注意,对于任意一个i>ki>kk \mod i=kkmodi=k


#include<cstdio>#include<algorithm>using namespace std;typedef long long ll;int main() {    ll n,k;    scanf("%lld%lld",&n,&k);    ll ans=n*k;    for(ll l=1,r;l<=n;l=r+1) {        if(k/l!=0) r=min(k/(k/l),n);         else r=n;        ans-=(k/l)*(r-l+1)*(l+r)/2;    }    printf("%lld",ans);    return 0;}


首先,我们处理一下n>=k的情况,因为这种情况很好处理:

G(n,k)=k%1+k%2+......+k%n,那么,当n>=k时,n>=k的部分k%i就是k,然后就是n<k了

这里,我们定义[c]为对c下取整.

我们不难发现在[k/2]+1到k这个区间里,k%i是一个等差数列,公差为1

在[k/3]+1到[k/2]这个区间里,k%i也是一个等差数列,公差为2

在[k/4]+1到[k/3]这个区间里,k%i也是一个等差数列,公差为3

.........

一直到[k/√k]+1到[k/(√k-1)]这个区间里,k%i也是一个等差数列,公差为(√k-1).

剩下的1到[k/√k]的区间直接暴力求解即可.

这样,我们就能用O(√k)来解决了.

再讨论一下比较难想的n<k的情况,

当n<k时,自然就不存在能直接算出k%i的部分了,

有人会问了,这不是更简单一些吗?直接不要超出的部分,再用上面给出的计算不就行了,

是的,大部分是这样,但是,我们需要考虑的是,有可能以上给出的区间中,n并不包含其中,也就是说没必要算那些不包含n的区间了.

那么,我们需要找一个区间,使n包含其中,剩下的都按上面的方法做即可.

long long n,i,k,l,ans;//注意:整型默认为下取整int main(){    cin>>n>>k;    if(n>=k){//n>=k的部分        ans=(n-k)*1ll*k;//k%i的结果都是k,共有(n-k)个    }    else{//否则n<k        for(i=1;i*i<=k&&i<n;i++){//找一个区间使得n在其中,i就是区间上界的分母(详情看解释)            if(k/i<n){//找到了                l=k/i;//记下来                break;//退出            }            l=i;//否则就是当前的i        }        l++;//这里l++是为了把分母移到区间的下界(详情看解释)        ans=1ll*(n-l+1)*(k%n+k/l*(n-l)+k%n)/2;//注意:这里是特殊处理含n的区间(其中上界是n,下界是l,公差为k/l)        //高斯速算公式:(首项+末项)*项数/2        //(n-l+1)相当于项数,也就是区间长度        //k%n是首项        //k/l*(n-l)+k%n是末项,因为公差是k/l,中间有(n-l)个数,首项是k%n    }    for(i=k/n+1;i*i<=k;i++){//两种情况的交集,也就是都需要枚举,一开始i=k/n+1,i<=√k,        //这里我们需要一个证明:为什么一开始i=k/n+1,当n>=k时,k/n+1刚好是1,也就是解释里第一个区间的上界分母        //当n<k时,因为刚刚已经处理过了包含n的区间,所以不必再处理,k/n+1刚好是包含n区间的下一个区间的上界分母        ans+=1ll*(k%i+i*(k/i-k/(i+1)-1)+k%i)*(k/i-k/(i+1))/2;        //高斯速算公式:(首项+末项)*项数/2        //这里首项是k%i        //末项是i*(k/i-k/(i+1)-1)+k%i,因为公差是i,其中一共有k/i-k/(i+1)-1个数,也就是项数-1(详情看解释)        //项数是k/i-k/(i+1)    }    n=k/i;//1到[k/√k]的区间暴力求解    for(i=1;i<=n;i++){        ans+=k%i;//每次ans+k%i    }    cout<<ans<<endl;    //system("pause");    return 0;}好了,做到这儿,是不是觉得很简单了呢?