质数
欧拉筛 O ( n ) O(n) O(n)
int prime[N],tot = 0;
bool notprime[N];
void get_prime() {
for(int i = 2; i <= N ;i++) {
if(!notprime[i]) prime[++tot] = i;
for(int j = 1; j <= tot && i * prime[j] <= N; j++) {
notprime[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
证明
在埃氏筛中每个合数会被其每个质因子筛一遍,考虑能否只被其最小的质因子筛,这样复杂度就能变为
O
(
n
)
O(n)
O(n)。
只用证明两件事:
- 每一个合数都会被筛到。
- 每一个合数都仅会被其最小质因子筛到
设被筛去的合数 c = i ∗ p r i m e [ j ] ( c < = N ) c = i * prime[j] (c <= N) c=i∗prime[j](c<=N)
- 对于
1
1
1,要证会被
i
∗
p
r
i
m
e
[
j
]
i * prime[j]
i∗prime[j]筛到,即证在代码中不会提前终止循环。
当 p r i m e [ j ] prime[j] prime[j] 为 c c c 的最小质因子时,首先显然 p r i m e [ j ] < i prime[j]<i prime[j]<i。由于 p r i m e [ j ] prime[j] prime[j] 为 c c c 的最小质因子,故 i i i 中没有比 p r i m e [ j ] prime[j] prime[j] 更小的质因子故不会出现 i m o d p r i m e [ k ] = = 0 ( k < j ) i \bmod prime[k] == 0(k < j) imodprime[k]==0(k<j)。 - 对于 2 2 2,假设会被非最小质因子 p r i m e [ j ] prime[j] prime[j] 筛去,此时 i i i 中含有比 p r i m e [ j ] prime[j] prime[j] 更小的质因子,显然会在某个 k ( k < j ) k(k < j) k(k<j) 时满足 i m o d p r i m e [ k ] = = 0 i \bmod prime[k] == 0 imodprime[k]==0 而终止循环。
质因数分解
算数基本定理
任何一个大于
1
1
1 的正整数都能唯一分解为有限个质数的乘积,可写作:
N
=
p
1
c
1
∗
p
2
c
2
∗
…
∗
p
m
c
m
(
p
1
<
p
2
<
…
<
p
m
)
N = p_1^{c_1} * p_2^{c_2} *…*p_m^{c_m}(p_1 < p_2 < … <p_m)
N=p1c1∗p2c2∗…∗pmcm(p1<p2<…<pm)
试除法(分解单个数) O ( n ) O(\sqrt{n}) O(n)
int m = 0;
void divide(int n) {
for(int i = 2; i <= sqrt(n); i++) {
if(n % i == 0) {
prime[++m] = i,c[m] = 0;
while(n % i == 0) n /= i,c[m]++;
}
}
if(n > 1) prime[++m] = n,c[m] = 1;
}
推荐练习
题解
分别把 1 − N 1 - N 1−N每个数分解质因数 O ( N N ) O(N\sqrt{N}) O(NN)显然会超时,所以我们可以先筛出 1 − N 1 - N 1−N中的质数,然后对于每个质数 p i p_i pi,求出 N ! N! N! 中共包含了几个质因子 p i p_i pi,在 1 − N 1 - N 1−N中 p i p_i pi 的倍数即至少包含一个质因子 p i p_i pi 的数显然有 ⌊ N p i ⌋ \left \lfloor \frac{N}{p_i} \right \rfloor ⌊piN⌋个,在 1 − N 1 - N 1−N中 p i 2 p_i^{2} pi2 的倍数即至少包含一个质因子 p i p_i pi 的数显然有 ⌊ N p i 2 ⌋ \left \lfloor \frac{N}{p_i^{2}} \right \rfloor ⌊pi2N⌋个,不过因为其中的一个质因子在 ⌊ N p i ⌋ \left \lfloor \frac{N}{p_i} \right \rfloor ⌊piN⌋中已经统计了,所以只需要再统计第 2 2 2个质因子,依次类推。
原因
可以类比以下代码
for(int i = p; i <= n; i += p)
其中的每一个
i
i
i 都至少包含一个质因子
p
p
p,这样的数显然有
⌊
N
p
i
⌋
\left \lfloor \frac{N}{p_i} \right \rfloor
⌊piN⌋个。
综上,
N
!
N!
N! 中质因子
p
i
p_i
pi 的个数为:
⌊
N
p
i
⌋
+
⌊
N
p
i
2
⌋
+
…
+
⌊
N
p
i
⌊
log
p
N
⌋
⌋
=
∑
p
i
k
≤
N
⌊
N
p
i
k
⌋
\left \lfloor \frac{N}{p_i} \right \rfloor +\left \lfloor \frac{N}{p_i^{2}} \right \rfloor + …+\left \lfloor \frac{N}{p_i^{\left \lfloor \log_p N \right \rfloor}} \right \rfloor = \sum_{p_i^{k}\leq N} \left \lfloor \frac{N}{p_i^{k}} \right \rfloor
⌊piN⌋+⌊pi2N⌋+…+⎣⎢⎢⎢pi⌊logpN⌋N⎦⎥⎥⎥=pik≤N∑⌊pikN⌋
代码
#include<cstdio>
const int N = 1e6 + 5;
int prime[N],tot = 0,n;
bool notprime[N];
void get_prime() {
for(int i = 2; i <= n ;i++) {
if(!notprime[i]) prime[++tot] = i;
for(int j = 1; j <= tot && i * prime[j] <= n; j++) {
notprime[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
int main() {
scanf("%d",&n);
get_prime();
for(int i = 1; i <= tot; i++) {
int p = prime[i],ans = 0;
for(long long j = p; j <= n; j *= p) ans += (n / j);
//注意一个细节,变量j会爆int
//可以变乘为除,代码如下
//for(int j = n; j; j /= p) ans += (j / p);
printf("%d %d\n",p,ans);
}
return 0;
}
约数
算数基本定理的推论
N
=
p
1
c
1
∗
p
2
c
2
∗
…
∗
p
m
c
m
N = p_1^{c_1} * p_2^{c_2} *…*p_m^{c_m}
N=p1c1∗p2c2∗…∗pmcm的正约数集合可以写作:
{
p
1
b
1
p
2
b
2
…
p
m
b
m
}
(
0
≤
b
i
≤
c
i
)
\left\{ p_1^{b_1} p_2^{b_2} …p_m^{b_m} \right\}(0\leq b_i \leq c_i)
{p1b1p2b2…pmbm}(0≤bi≤ci)
N 的正约数个数为:
( c 1 + 1 ) ∗ ( c 2 + 1 ) ∗ … ∗ ( c m + 1 ) = ∏ i = 1 m ( c i + 1 ) (c_1+1) * (c_2+1) * …* (c_m+1) = \prod_{i = 1}^m (c_i+1) (c1+1)∗(c2+1)∗…∗(cm+1)=i=1∏m(ci+1)
N 的所有正约数的和为:
( 1 + p 1 + p 1 2 + … + p 1 c 1 ) ∗ … ∗ ( 1 + p m + p m 2 + … + p m c m ) = ∏ i = 1 m ( ∑ j = 0 c i ( p i ) j ) (1+p_1+p_1^{2}+…+p_1^{c_1})*…*(1+p_m+p_m^{2}+…+p_m^{c_m}) = \prod_{i = 1}^{m} (\sum_{j = 0} ^{c_i} (p_i)^j) (1+p1+p12+…+p1c1)∗…∗(1+pm+pm2+…+pmcm)=i=1∏m(j=0∑ci(pi)j)
求 N 的正约数集合——试除法 O ( N ) O(\sqrt{N}) O(N)
int factor[1600],m = 0;
for(int i = 1; i * i <= n; i++)
if(n % i == 0) {
factor[++m] = i;
if(i != n / i) factor[++m] = n / i;
}
注意 f a c t o r [ ] factor[] factor[]中的数不是从小到大的顺序,而是成对的。
求1 ~ N 每个数的正约数集合——倍数法 O ( N log N ) O(N\log N) O(NlogN)
#include<cstdio>
#include<vector>
const int N = 20;
std::vector<int> factor[N];
int main() {
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N / i; j++)
factor[i * j].push_back(i);
for(int i = 1; i <= N; i++) {
for(int j = 0; j < factor[i].size(); j++)
printf("%d ",factor[i][j]);
puts("");
}
return 0;
}