题目陈述

大意:给定一个正整数nnnnnn表示为n=p×k+mn=p\times k + mn=p×k+m。即,nnn充当被除数,对于p,1pnp,1 \leq p \leq np,1pn,充当除数,然后得到对应的余数mmm,求对于所有的除数p1pnp(1 \leq p \leq n)p1pn的商kkk与余数mmm的乘积之和,即k=n/pkm\sum_{k=\lfloor n/p\rfloor} k*mk=n/pkm,答案取模modmodmod

算法一:朴素算法

算法思路

  • 根据题意,一个很显然的算法,就是对于每一个ppp分别计算出他的k,mk,mk,m然后计算kmk*mkm

代码实现

class Solution {
public:
    long long cowModCount(long long n) {
        long long ans = 0, mod = 1e9 + 7, k, m;
        for (int p = 1; p <= n; p ++ ) //枚举所有合法的p
        {
            k = n / p; //求解商
            m = n % p; //求解余数
            ans = (ans + k * m % mod) % mod; //将他们的贡献加入到答案中
        }
        return ans;
    }
};

复杂度分析

  • 时间复杂度,每个合法的ppp都遍历了一遍,即为ppp的区间大小,时间复杂度为O(n)O(n)O(n),当然nmax=INT_MAXn_{max}=INT\_MAXnmax=INT_MAX,显然还是会TLETLETLE
  • 空间复杂度,定义了四个变量ans,mod,k,mans,mod,k,mans,mod,k,m,为O(1)O(1)O(1)

算法二:数学推导+整除分块

前置知识:整除分块

  • 此处我们我们需要先了解一个前置知识:整除分块
  • 首先,要了解整除分块是干什么的?设计整除分块的题目大概都具有以下形式 i=1n<mstyle displaystyle="true" scriptlevel="0">ni</mstyle>\sum_{i=1}^n \lfloor \cfrac{n}{i}\rfloori=1nin
  • 上面的括号代表向下取整,即整除
  • 我们以n=6n=6n=6为例子 图片说明 图片说明
  • 我们可以发现对于一个iii,往往是一段区间的整除值都是同一个值(废话)
  • 假如我们通常知道了区间内的一个下标(一般是左端点),如何求得跟它所在区间的右端点
  • n=ij+tt[0,i1]n=i*j+t,t\in [0,i-1]n=ij+tt[0,i1],显然我们可以这样表示iii,而把jjj设置为倍数
  • 对于右端点rrr,必然有t<jt<jt<j
  • 反证法,若t>=jt>=jt>=j则,可以令t=tjt'=t-jt=tj,n=rj+t=(r+1)j+tn=r*j+t=(r+1)*j+t'n=rj+t=(r+1)j+t
  • 就得到了区间内比右端rrr还靠右的满足条件的点
  • 所以:对于右端点rrr,必然有t<jt<jt<j
  • 我们就可以根据这个性质,通过lll,求得jjj,再通过jjj求得rrr
  • 故对于一个区间,我们知道左端点lll,即可以通过公式计算右端点r=<mstyle displaystyle="true" scriptlevel="0">n<mstyle displaystyle="true" scriptlevel="0">nl</mstyle></mstyle>r = \lfloor \cfrac{n}{\lfloor \cfrac{n}{l}\rfloor}\rfloorr=lnn,代码中写作r = n / (n / i),注意此处为整除
  • 时间复杂度为O(n)O(\sqrt n)O(n)

算法思路

  • 显然这是一道数学题,我们考虑数学方法,当然,也少不了推导和化简
  • 对于所有的p,(p[1,n])p,(p\in[1,n])p,(p[1,n])
  • ans=<mstyle displaystyle="true" scriptlevel="0">np</mstyle>×(n%p)ans =\sum \lfloor \cfrac{n}{p}\rfloor \times (n \% p)ans=pn×(n%p)
  • ans=p=1n<mstyle displaystyle="true" scriptlevel="0">np</mstyle>×(np×<mstyle displaystyle="true" scriptlevel="0">np</mstyle>)ans=\sum_{p=1}^{n} \lfloor \cfrac{n}{p}\rfloor \times (n - p \times \lfloor \cfrac{n}{p}\rfloor)ans=p=1npn×(np×pn)
  • 整除分块思想有,k[l,r]k \in[l,r]k[l,r],其中r=<mstyle displaystyle="true" scriptlevel="0">n<mstyle displaystyle="true" scriptlevel="0">nl</mstyle></mstyle>r = \lfloor \cfrac{n}{\lfloor \cfrac{n}{l}\rfloor}\rfloorr=lnn
  • 同一块<mstyle displaystyle="true" scriptlevel="0">nk</mstyle>\lfloor \cfrac{n}{k}\rfloorkn都相同,即<mstyle displaystyle="true" scriptlevel="0">nk</mstyle>=<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>\lfloor \cfrac{n}{k}\rfloor=\lfloor \cfrac{n}{l}\rfloorkn=ln
  • 故对于同一块有k=lr<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>×(nk×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>)\sum_{k=l}^{r} \lfloor \cfrac{n}{l}\rfloor \times (n-k \times \lfloor \cfrac{n}{l}\rfloor)k=lrln×(nk×ln)
  • 展开括号得 k=lr(n×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>k×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>2)\sum_{k=l}^{r}(n\times \lfloor \cfrac{n}{l}\rfloor-k\times\lfloor \cfrac{n}{l}\rfloor ^2)k=lr(n×lnk×ln2)
  • 求和符号性质有 k=lrn×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>k=lrk×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>2\sum_{k=l}^{r}n\times \lfloor \cfrac{n}{l}\rfloor -\sum_{k=l}^{r}k\times \lfloor \cfrac{n}{l}\rfloor^2k=lrn×lnk=lrk×ln2
  • len=rl+1len=r-l+1len=rl+1
  • 化简求和结果,等差数列公式有 len×n×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle><mstyle displaystyle="true" scriptlevel="0">(l+r)×len2</mstyle>×k×<mstyle displaystyle="true" scriptlevel="0">nl</mstyle>2len\times n \times \lfloor \cfrac{n}{l}\rfloor - \cfrac{(l+r)\times len}{2}\times k \times \lfloor \cfrac{n}{l}\rfloor^2len×n×ln2(l+r)×len×k×ln2
  • 现在已知每一块的O(1)O(1)O(1)求法
  • 整除分块得到了每一块的答案即可求得ansansans
  • 下面是我的手写推导过程 图片说明

关于不用逆元

  • 肯定有同学好奇为什么我代码里面除了222,但是没有用逆元?
  • 因为那一整项求和公式,必然能整除2,故不需要逆元
  • 证明如下:
  • 只要(l+r)(l+r)(l+r)lenlenlen中有一个为偶数,就可以整除2
  • l,rl,rl,r奇偶性相同时候,(l+r)(l+r)(l+r)为偶数,len=rl+1len=r-l+1len=rl+1为奇数,能整除
  • l,rl,rl,r奇偶性不同的时候,(l+r)(l+r)(l+r)为奇数,len=rl+1len=r-l+1len=rl+1为偶数,能整除
  • 故此处不需要用到逆元

复杂度分析

  • 时间复杂度,整除分块为O(n)O(\sqrt n)O(n),对于每一块表达式求值为O(1)O(1)O(1),总得时间复杂度为O(n)O(\sqrt n)O(n)
  • 空间复杂度,定义了几个变量,为O(1)O(1)O(1)

代码实现

class Solution {
public:
    long long cowModCount(long long n) {
        long long ans = 0, mod = 1e9 + 7;
        for (long long l = 1, r, len, tmp; l <= n; l = r + 1)
        {
            tmp = n / l;//重复计算所以用tmp暂存
            r = n / tmp;//根据公式计算右边界
            len = r - l + 1;//计算区间长度
            ans = (ans + len * tmp * n % mod - tmp * tmp % mod * (l + r) * len / 2 % mod) % mod;
        }//根据我们上面计算的公式,累加到ans值上面
        if (ans < 0)
            ans += mod;//C++里面负数取模依旧为负数,需要加上mod
        return ans;
    }
};