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

传送门

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

你可能感兴趣的:(逆元,推公式,基础数论)