51 NOD:1225 余数之和(推公式)

来源:互联网 发布:linux 挂载分区成功 编辑:程序博客网 时间:2024/05/21 22:45

传送门

1225 余数之和
基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注
F(n) = (n % 1) + (n % 2) + (n % 3) + …… (n % n)。其中%表示Mod,也就是余数。
例如F(6) = 6 % 1 + 6 % 2 + 6 % 3 + 6 % 4 + 6 % 5 + 6 % 6 = 0 + 0 + 0 + 2 + 1 + 0 = 3。
给出n,计算F(n), 由于结果很大,输出Mod 1000000007的结果即可。
Input
输入1个数N(2 <= N <= 10^12)。
Output
输出F(n) Mod 1000000007的结果。
Input示例
6
Output示例
3

解题思路:
首先我们知道的是求n 的余数 n - [n/i]*i(在这里”[]”表示的是取整的意思)那么我们要求的余数之和就是 sigma(n-[n/i]*i)也就是

n2i=1n([n/i]i)

我们知道的是 当 n/i > sqrt(n)的时候n/i的数值只出现一次,然后我们看一下这个题的数据范围,也知道暴力解决不了,那么我们就得采用的是O(sqrt(n))的算法了,那么我们现在从 i=1循环到 i=sqrt(n),对于n/i>sqrt(n)的来说直接暴力求解就行了,因为n/i要是想>sqrt(n)话,i一定是 < sqrt(n)的 那么我们剩下的sqrt(n)-n怎么求呢,我们可以观察一下,在 i = sqrt(n)-n的范围内 n/i 一定是 <= sqrt(n)的,那么我们可以根据这个规律来求,因为每次他们都是连续递增出现的,所以可以将 [n/i] * i 看作一个等差数列,在这个数列中 因为在一段数中[n/i]是不变的,变化的只有i,而且i是连续递增变化的,那么首项是n/(i+1)+1,末项是 n/i,个数是 n/i - n/(i+1) ,别忘记乘以i,然后除以2(这里需要用到逆元),其实在纸上一推就很明显了,再结合代码来看就很容易明白了:
上代码:

#include <iostream>#include <cmath>#include <cstdio>#include <cstring>#include <cstdlib>using namespace std;typedef long long LL;const LL MOD = 1000000007;void Ex_gcd(LL a, LL b, LL &x, LL &y){    if(b == 0)    {        x = 1;        y = 0;        return;    }    LL x1, y1;    Ex_gcd(b, a%b, x1, y1);    x = y1;    y = x1-(a/b)*y1;}int main(){    /**求的逆元    LL inv, y;    Ex_gcd(2, MOD, inv, y);    inv = (inv%MOD+MOD)%MOD;    **/    LL inv = 500000004;    LL n;    while(~scanf("%I64d",&n))    {        LL ans = ((n%MOD) * (n%MOD)) % MOD;///n^2        LL m = (LL)sqrt(n);///放在外面省时间        for(LL i=1; i<=m; i++)        {            if(n/i == m)///特判一下sqrt(n)            {                ans = ((ans-m*m)%MOD+MOD)%MOD;                continue;            }            LL tmp = (n/i+n/(i+1)+1)%MOD;///n/i是等差数列的第一项,n/(i+1)+1最后一项            LL cur = (i*(n/i-n/(i+1)))%MOD;///n/i-n/(i+1)是个数            tmp = ((tmp*cur)%MOD*inv)%MOD;            tmp = (tmp+(n/i)*i)%MOD;            ans = ((ans-tmp)%MOD+MOD)%MOD;        }        printf("%I64d\n",ans);    }    return 0;}
0 0
原创粉丝点击