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