题目陈述
大意:给定一个正整数n,n表示为n=p×k+m。即,n充当被除数,对于p,1≤p≤n,充当除数,然后得到对应的余数m,求对于所有的除数p(1≤p≤n)的商k与余数m的乘积之和,即∑k=⌊n/p⌋k∗m,答案取模mod
算法一:朴素算法
算法思路
- 根据题意,一个很显然的算法,就是对于每一个p分别计算出他的k,m然后计算k∗m
代码实现
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;
}
};
复杂度分析
- 时间复杂度,每个合法的p都遍历了一遍,即为p的区间大小,时间复杂度为O(n),当然nmax=INT_MAX,显然还是会TLE的
- 空间复杂度,定义了四个变量ans,mod,k,m,为O(1)
算法二:数学推导+整除分块
前置知识:整除分块
- 此处我们我们需要先了解一个前置知识:整除分块。
- 首先,要了解整除分块是干什么的?设计整除分块的题目大概都具有以下形式 ∑i=1n⌊in⌋
- 上面的括号代表向下取整,即整除
- 我们以n=6为例子
- 我们可以发现对于一个i,往往是一段区间的整除值都是同一个值(废话)
- 假如我们通常知道了区间内的一个下标(一般是左端点),如何求得跟它所在区间的右端点?
- 设n=i∗j+t,t∈[0,i−1],显然我们可以这样表示i,而把j设置为倍数
- 对于右端点r,必然有t<j
- 反证法,若t>=j则,可以令t′=t−j,n=r∗j+t=(r+1)∗j+t′
- 就得到了区间内比右端r还靠右的满足条件的点
- 所以:对于右端点r,必然有t<j
- 我们就可以根据这个性质,通过l,求得j,再通过j求得r
- 故对于一个区间,我们知道左端点l,即可以通过公式计算右端点r=⌊⌊ln⌋n⌋,代码中写作
r = n / (n / i)
,注意此处为整除 - 时间复杂度为O(n)
算法思路
- 显然这是一道数学题,我们考虑数学方法,当然,也少不了推导和化简
- 对于所有的p,(p∈[1,n])
- 有ans=∑⌊pn⌋×(n%p)
- 即 ans=∑p=1n⌊pn⌋×(n−p×⌊pn⌋)
- 整除分块思想有,k∈[l,r],其中r=⌊⌊ln⌋n⌋
- 同一块⌊kn⌋都相同,即⌊kn⌋=⌊ln⌋
- 故对于同一块有∑k=lr⌊ln⌋×(n−k×⌊ln⌋)
- 展开括号得 ∑k=lr(n×⌊ln⌋−k×⌊ln⌋2)
- 求和符号性质有 ∑k=lrn×⌊ln⌋−∑k=lrk×⌊ln⌋2
- 令len=r−l+1
- 化简求和结果,等差数列公式有 len×n×⌊ln⌋−2(l+r)×len×k×⌊ln⌋2
- 现在已知每一块的O(1)求法
- 整除分块得到了每一块的答案即可求得ans
- 下面是我的手写推导过程
关于不用逆元
- 肯定有同学好奇为什么我代码里面除了2,但是没有用逆元?
- 因为那一整项求和公式,必然能整除2,故不需要逆元
- 证明如下:
- 只要(l+r)和len中有一个为偶数,就可以整除2
- l,r奇偶性相同时候,(l+r)为偶数,len=r−l+1为奇数,能整除
- l,r奇偶性不同的时候,(l+r)为奇数,len=r−l+1为偶数,能整除
- 故此处不需要用到逆元
复杂度分析
- 时间复杂度,整除分块为O(n),对于每一块表达式求值为O(1),总得时间复杂度为O(n)
- 空间复杂度,定义了几个变量,为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;
}
};