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)\)
只用证明两件事:

  1. 每一个合数都会被筛到。
  2. 每一个合数都仅会被其最小质因子筛到

设被筛去的合数 \(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;
}
推荐练习

反素数

最大公约数

posted @ 2021-05-29 20:08  init-神眷の樱花  阅读(247)  评论(0)    收藏  举报