257 lines
8.6 KiB
Markdown
257 lines
8.6 KiB
Markdown
|
|
## 线性筛求 <font color='blue'>约数个数</font> 与 <font color='blue'>约数和</font>
|
|||
|
|
|
|||
|
|
线性筛,顾名思义,就是欧拉筛,在线性时间内可以求出答案,也就是$O(N)$的时间,非常牛$X$的效率。
|
|||
|
|
|
|||
|
|
### 一、约数个数
|
|||
|
|
|
|||
|
|
根据数字唯一分解定理,设
|
|||
|
|
$$\LARGE n=p_1^{r_1}*p_2^{r_2}*p_3^{r_3}*...*p_k^{r_k}$$
|
|||
|
|
|
|||
|
|
对于每个$n$的约数,一定是由以上质因数$p_1 \sim p_k$相乘而来,根据乘法原理,每个质因数都可以选择$0$到$r_i$这$r_i+1$个选择。
|
|||
|
|
|
|||
|
|
$n$的约数个数即为
|
|||
|
|
|
|||
|
|
$$\LARGE d(n)=(r_1+1)\times (r_2+1) \times ... \times (r_k+1) =\prod_{i=1}^k(r_i+1)$$
|
|||
|
|
|
|||
|
|
**筛的过程中需要保存$n$的最小质因子的出现个数即$r_1$,原因后面会说明:**
|
|||
|
|
|
|||
|
|
<font color='red' size=4><b>
|
|||
|
|
* $d[i]$记录$i$的约数个数
|
|||
|
|
* $num[i]$记录$i$的最小质因数个数
|
|||
|
|
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
分情况讨论:
|
|||
|
|
|
|||
|
|
#### (一)、$i$是质数
|
|||
|
|
$$
|
|||
|
|
\large \left\{\begin{matrix}
|
|||
|
|
d[i]=2 & \\
|
|||
|
|
num[i]=1 &
|
|||
|
|
\end{matrix}\right.
|
|||
|
|
$$
|
|||
|
|
|
|||
|
|
* $d[i]$:$i$是质数,所以约数只有$1$和自身,约数个数为$2$
|
|||
|
|
* $num[i]$:$i$是质数,最小质因子就是自己,个数是$1$
|
|||
|
|
|
|||
|
|
#### (二)、$i$取模枚举的第$j$个质数为$0$, $primes[j]$整除$i$
|
|||
|
|
<font color='blue' size=4><b>$Q1:$我们在研究哪些数字之间的递推关系?</b></font>
|
|||
|
|
$A:$既然是递推,一般都是已知小的推出大的,这里应该是已知$d[i]$推出$d[i*primes[j]]$的值。
|
|||
|
|
|
|||
|
|
<font color='blue' size=4><b>$Q2$:$d[i*primes[j]]$是啥?</b></font>
|
|||
|
|
$A$: <font color='red' size=4><b>
|
|||
|
|
$primes[j]$是$i$的因子,$i*primes[j]$唯一分解不会产生新的质因子,依然是$p_1,p_2,...p_k$
|
|||
|
|
$primes[j]$从小到大枚举,所以一定是$i$的最小质因子!所以$p_1^{r_1+1}$,按唯一分解定理展开:
|
|||
|
|
|
|||
|
|
$$\large d[i*primes[j]]=(1+r_1+1)*......*(1+r_k)$$
|
|||
|
|
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
<font color='blue' size=4><b>$Q3$:$d[i∗primes[j]]$怎么从$d[i]$转移?</b></font>
|
|||
|
|
|
|||
|
|
这个时候就可以用到我们之前维护的$num[i]$了。
|
|||
|
|
|
|||
|
|
转移也就非常简单了:
|
|||
|
|
|
|||
|
|
$$\large d[i∗primes[j]]=d[i]/(num[i]+1)∗(num[i]+2)$$
|
|||
|
|
|
|||
|
|
<font color='red' size=4><b>解释:
|
|||
|
|
|
|||
|
|
$$
|
|||
|
|
\large \left\{\begin{matrix}
|
|||
|
|
d(i)=(1+r_1)*......*(1+r_k) & ①\\
|
|||
|
|
d(i*primes[j])=(1+r_1+1)*......*(1+r_k) & ②\\
|
|||
|
|
\end{matrix}\right.
|
|||
|
|
$$
|
|||
|
|
|
|||
|
|
使用瞪眼大法(观察法),找出两者之间的递推关系,很明显,就是 ①式除以$1+r_1$,再乘上$1+r_1+1$即可转化为 ②式,这下明白为什么要记录$num[i]$最小质因子的个数了吧~
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
<font color='blue' size=4><b>$Q4$:那么$num[i∗primes[j]]$是不是也需要从$num[i]$进行转移呢?</b></font>
|
|||
|
|
$A$:$num[i*primes[j]]$也要转移,加上最小质因子$primes[j]$的贡献也就是:
|
|||
|
|
|
|||
|
|
$$\large num[i∗primes[j]]=num[i]+1$$
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### (三)、当前数取模枚举的第$j$个质数不为$0$,即 $primes[j]$ 不能整除 $i$
|
|||
|
|
|
|||
|
|
首先我们知道$i∗primes[j]$这个数中之前一定不包含$primes[j]$这个质因数,
|
|||
|
|
|
|||
|
|
那么约数个数要加上$prime[j]$的,也就是:
|
|||
|
|
|
|||
|
|
$$\large d[i*primes[j]]=(1+r_1)*...*(1+r_k)*(1+1) \\
|
|||
|
|
=d[i]*2=d[i]*d[primes[j]] $$
|
|||
|
|
|
|||
|
|
|
|||
|
|
然后对于最小质因子,因为$j$是从小到大枚举的,所以$i∗primes[j]$这个数的最小质因子也就是$primes[j]$
|
|||
|
|
|
|||
|
|
所以就可以得到:
|
|||
|
|
|
|||
|
|
$$\large num[i*primes[j]]=1$$
|
|||
|
|
|
|||
|
|
综上,就可以写出筛质因数个数的代码了:
|
|||
|
|
|
|||
|
|
```c++
|
|||
|
|
#include <iostream>
|
|||
|
|
#include <cstdio>
|
|||
|
|
#include <cstring>
|
|||
|
|
using namespace std;
|
|||
|
|
const int N = 1010;
|
|||
|
|
|
|||
|
|
int primes[N], cnt; // primes[]存储所有素数
|
|||
|
|
bool st[N]; // st[x]存储x是否被筛掉
|
|||
|
|
int d[N]; // d[x]表示x的约数个数
|
|||
|
|
int num[N]; // num[x]表示x的最小质因数的个数
|
|||
|
|
int n;
|
|||
|
|
|
|||
|
|
//欧拉筛法+求约数个数
|
|||
|
|
void get_primes(int n) {
|
|||
|
|
d[1] = 1; // 1的约数只有1个,这个比较特殊
|
|||
|
|
|
|||
|
|
for (int i = 2; i <= n; i++) {
|
|||
|
|
if (!st[i]) {
|
|||
|
|
primes[cnt++] = i;
|
|||
|
|
// i是质数
|
|||
|
|
d[i] = 2; //约数个数是2个,一个是1,另一个是i
|
|||
|
|
num[i] = 1; //最小质因子个数是1,最小质因子就是自己i
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int j = 0; i * primes[j] <= n; j++) {
|
|||
|
|
st[i * primes[j]] = true;
|
|||
|
|
if (i % primes[j] == 0) {
|
|||
|
|
d[i * primes[j]] = d[i] / (num[i] + 1) * (num[i] + 2);
|
|||
|
|
num[i * primes[j]] = num[i] + 1;
|
|||
|
|
break;
|
|||
|
|
} else {
|
|||
|
|
// d[i * primes[j]] = d[i] * d[primes[j]]; 等价于下面的代码
|
|||
|
|
d[i * primes[j]] = d[i] * 2;
|
|||
|
|
num[i * primes[j]] = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
scanf("%d", &n);
|
|||
|
|
get_primes(n);
|
|||
|
|
//输出1~n之间所有数字的约数个数
|
|||
|
|
for (int i = 1; i <= n; i++) printf("%d %d\n", i, d[i]);
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 二、约数和
|
|||
|
|
|
|||
|
|
<font color='red' size=4><b>设$sd[i]$表示$i$的约数和</b></font>
|
|||
|
|
|
|||
|
|
根据算数基本定理得:[约数和参考链接](https://www.cnblogs.com/littlehb/p/15341428.html)
|
|||
|
|
|
|||
|
|
$$\large sd[n]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})$$
|
|||
|
|
|
|||
|
|
那么根据这个式子就可以开始干了。。。
|
|||
|
|
|
|||
|
|
<font color='red' size=4><b>
|
|||
|
|
设$num[i]$表示最小质因子的那一项:
|
|||
|
|
$$\large num[i]= p_1^0+p_1^1+p_1^2+……+p_1^{r_1}$$
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
|
|||
|
|
分情况讨论:
|
|||
|
|
|
|||
|
|
#### (一)、$i$是质数
|
|||
|
|
$$\large sd[i]=num[i]=i+1$$
|
|||
|
|
<font color='red' size=4><b>解释:
|
|||
|
|
* $i$是质数,它的约数只有自己和$1$,约数和是$i+1$
|
|||
|
|
* $i$是质数,质因子分解也只能分解出自己,$sd[i]=i^0+i^1=1+i$
|
|||
|
|
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### (二)、$i$取模枚举的质数$primes[j]$不为0,即$primes[j]$不能整除$i$
|
|||
|
|
<font color='red' size=4><b>注:下面的讨论中使用$p_j$代替$primes[j]$</b></font>
|
|||
|
|
|
|||
|
|
$i∗p_j$里原先没有$p_j$这一项,加上这一项之后:
|
|||
|
|
|
|||
|
|
$$\large \because sd[i]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})$$
|
|||
|
|
|
|||
|
|
$$\large \because sd[i*p_j]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})*(p_j^0+p_j^1)$$
|
|||
|
|
|
|||
|
|
$\large \because p_j$ 是质数 $\large \therefore sd[p_j]=p_j^0+p_j^1$
|
|||
|
|
|
|||
|
|
$\large \therefore sd[i*p_j]=sd[i]*sd[p_j]$ <font color='red' size=4><b>注:积性函数</b></font>
|
|||
|
|
|
|||
|
|
|
|||
|
|
**同时更新一下$num[i∗p_j]$**
|
|||
|
|
|
|||
|
|
$$\large num[i∗p_j]=num[p_j]=p_j^0+p_j^1=1+p_j$$
|
|||
|
|
|
|||
|
|
<font color='red' size=4><b>解释:
|
|||
|
|
因为质因子从小到大枚举,那么$i∗p_j$的最小质因子就应该是$p_j$,那么$num[i∗p_j]$也就应该等于$num[p_j]$
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
#### (三)、$i$取模枚举的质数$j$为0,即 $primes[j]$整除$i$
|
|||
|
|
|
|||
|
|
那么$sd[i∗p_j]$中的第一项也就是$num[i∗p_j]$也一定是$p_j$的第一项
|
|||
|
|
|
|||
|
|
<font color='red' size=4><b>解释:(等比数列变形小技巧)</b></font>
|
|||
|
|
$1+p_i+p_i^2+……+p_i^{r_i}$这个时候要变成$1+p_i+p_i^2+……+p_i^{r_i}+p_i^{r_i+1}$,那么只需要所有的都乘以一个$p_i$,然后再加一个$1$就好了。
|
|||
|
|
<font color='red' size=4><b>
|
|||
|
|
$$\large (1+p_i+p_i^2+……+p_i^{r_i})*p_i+1= \\
|
|||
|
|
1+p_i+p_i^2+……+p_i^{r_i}+p_i^{r_i+1}$$
|
|||
|
|
</b></font>
|
|||
|
|
|
|||
|
|
**结论:**
|
|||
|
|
|
|||
|
|
$$\large num[i∗p_j]=num[i]∗p_j+1$$
|
|||
|
|
|
|||
|
|
这样,我们又可以开始愉快的写代码啦...
|
|||
|
|
|
|||
|
|
```c++
|
|||
|
|
#include <bits/stdc++.h>
|
|||
|
|
using namespace std;
|
|||
|
|
typedef long long LL;
|
|||
|
|
|
|||
|
|
const int N = 1e3 + 10;
|
|||
|
|
|
|||
|
|
int primes[N], cnt;
|
|||
|
|
bool st[N];
|
|||
|
|
|
|||
|
|
int num[N]; //最小质因子p1组成的等比序列 p1^0+p1^1+...+p1^r1
|
|||
|
|
int sd[N]; //约数和
|
|||
|
|
|
|||
|
|
int n;
|
|||
|
|
|
|||
|
|
void get_primes(int n) {
|
|||
|
|
sd[1] = 1; // 1的约数只有自己,约数和是1
|
|||
|
|
|
|||
|
|
for (int i = 2; i <= n; i++) {
|
|||
|
|
if (!st[i]) {
|
|||
|
|
primes[cnt++] = i;
|
|||
|
|
//质数
|
|||
|
|
sd[i] = num[i] = i + 1; // 1和自身是约数,约数和为i+1; 同时因为i是质数,所以只有一个分拆项,并且分拆项内容=pj^0+pj^1=1+pj=1+i
|
|||
|
|
}
|
|||
|
|
for (int j = 0; primes[j] * i <= n; j++) {
|
|||
|
|
st[i * primes[j]] = true;
|
|||
|
|
|
|||
|
|
if (i % primes[j]) {
|
|||
|
|
sd[i * primes[j]] = sd[i] * sd[primes[j]]; //积性函数
|
|||
|
|
num[i * primes[j]] = primes[j] + 1;
|
|||
|
|
} else {
|
|||
|
|
sd[i * primes[j]] = sd[i] / num[i] * (num[i] * primes[j] + 1);
|
|||
|
|
num[i * primes[j]] = num[i] * primes[j] + 1;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
scanf("%d", &n);
|
|||
|
|
get_primes(n);
|
|||
|
|
//约数和要不要自己本身,这里是不想要自己本身
|
|||
|
|
for (int i = 1; i <= n; i++) printf("%d ", sd[i] - i);
|
|||
|
|
puts("");
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
```
|