21.3.12 数论总结(一) 质数&因数
质数
欧拉筛 \(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)\)。
只用证明两件事:
- 每一个合数都会被筛到。
- 每一个合数都仅会被其最小质因子筛到
设被筛去的合数 \(c = i * prime[j] (c <= N)\)
- 对于 \(1\),要证会被 \(i * prime[j]\)筛到,即证在代码中不会提前终止循环。
当 \(prime[j]\) 为 \(c\) 的最小质因子时,首先显然 \(prime[j]<i\)。由于 \(prime[j]\) 为 \(c\) 的最小质因子,故 \(i\) 中没有比 \(prime[j]\) 更小的质因子故不会出现 \(i \bmod prime[k] == 0(k < j)\)。 - 对于 \(2\),假设会被非最小质因子 \(prime[j]\) 筛去,此时 \(i\) 中含有比 \(prime[j]\) 更小的质因子,显然会在某个 \(k(k < j)\) 时满足 \(i \bmod prime[k] == 0\) 而终止循环。
质因数分解
算数基本定理
任何一个大于 \(1\) 的正整数都能唯一分解为有限个质数的乘积,可写作:
\[N = p_1^{c_1} * p_2^{c_2} *…*p_m^{c_m}(p_1 < p_2 < … <p_m)
\]
试除法(分解单个数)\(O(\sqrt{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\)每个数分解质因数\(O(N\sqrt{N})\)显然会超时,所以我们可以先筛出\(1 - N\)中的质数,然后对于每个质数 \(p_i\),求出 \(N!\) 中共包含了几个质因子 \(p_i\),在\(1 - N\)中 \(p_i\) 的倍数即至少包含一个质因子 \(p_i\) 的数显然有\(\left \lfloor \frac{N}{p_i} \right \rfloor\)个,在\(1 - N\)中 \(p_i^{2}\) 的倍数即至少包含一个质因子 \(p_i\) 的数显然有\(\left \lfloor \frac{N}{p_i^{2}} \right \rfloor\)个,不过因为其中的一个质因子在\(\left \lfloor \frac{N}{p_i} \right \rfloor\)中已经统计了,所以只需要再统计第\(2\)个质因子,依次类推。
原因
可以类比以下代码
for(int i = p; i <= n; i += p)
其中的每一个 \(i\) 都至少包含一个质因子 \(p\),这样的数显然有\(\left \lfloor \frac{N}{p_i} \right \rfloor\)个。
综上,\(N!\) 中质因子 \(p_i\) 的个数为:
\[\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
\]
代码
#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}\)的正约数集合可以写作:
\[\left\{ p_1^{b_1} p_2^{b_2} …p_m^{b_m} \right\}(0\leq b_i \leq c_i)
\]
N 的正约数个数为:
\[(c_1+1) * (c_2+1) * …* (c_m+1) = \prod_{i = 1}^m (c_i+1)
\]
N 的所有正约数的和为:
\[(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)
\]
求 N 的正约数集合——试除法\(O(\sqrt{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;
}
注意\(factor[]\)中的数不是从小到大的顺序,而是成对的。
求1 ~ N 每个数的正约数集合——倍数法\(O(N\log N)\)
#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;
}

浙公网安备 33010602011771号