质数
定义
只存在和它本身两个约数的数。
质数分布较稀疏,对于一个正整数,不超过的质数大约有个。
质数的判定
1、试除法
若一个正整数为合数,则一定存在一个,使且。
证明:
由质数的定义可知,一定存在一个正整数,使且。
若且,则当且仅当时,才成立。故此时。
若且,则。故当时,。
综上述,对于每个合数,一定存在,使,且。
根据此命题,我们只需扫描内所有的数,判断其是否是的约数即可。
bool prime(int n) {
for (int i = 2; i <= n; ++i)
if (n % i == 0) return false;
return true;
}
时间复杂度
2、Eratosthenes筛法
在试除法中,我们通过枚举一个数的约数来判断其是否是质数。而筛法的思想与其相反。对于每个数,我们枚举它的倍数,易证其倍数一定为合数。对于每个合数,我们将其“筛去”,而剩下的数就是质数了。
观察上图,可以发现,只需对每个质数,从开始枚举倍数,并将其筛去,即可得到质数表。
void sieve(int n, bool *prime) {
memset(prime, true, sizeof is_prime);
prime[1] = false;
for (int i = 2; i <= n; ++i) {
if (!prime[i]) continue;
for (int j = i * i; j <= n; j += i)
prime[j] = false;
}
}
时间复杂度为 ,效率接近线性。
3、线性筛
尽管加了许多优化,Eratosthenes筛法仍然会重复标记合数。可以这样想,如果能够确定每个合数会被哪个质数筛去,就可以在线性时间复杂度内筛除质数表。
于是维护一个数组,表示的最小质因数。同时维护一个数组,记录质数表。
void sieve(int n, int &pr, int *p, int *v) {
for (int i = 2; i <= n; ++i) {
if (v[i] == 0) v[i] = i, p[++pr] = i;
// 没被筛,是质数
for (int j = 1; j <= pr; ++j) {
int k = i * p[j];
if (i % p[j] == 0 || k > n) break;
// 有比p[j]更小的质因子或超出范围
v[k] = p[j];
}
}
}
质因数分解
算数基本定理(唯一分解定理)
任何一个大于1的自然数 ,都可以唯一分解成有限个质数的乘积。
即 ,其中为质数且,为正整数。
这样的分解叫做的标准分解式。
质因数分解算法
1、试除法
对于一个正整数,可以证明至多存在一个大于的质因数。所以只需扫描之间的数。对于每个被扫描的数,若为质数且,则将除尽,并统计其个数。
void decom(int n, int &m, int *p, int *c) {
// 分解 n,分解式储存在 p[1..m] 及 c[1..m] 中
m = 0;
for (int i = 2, size = sqrt(n); i <= size; ++i)
if (n % i == 0) {
p[++m] = i, c[m] = 0;
for (; n % i == 0; n /= i)
++c[m];
}
if (n > 1) p[++m] = n, c[m] = 1; // 特判 n 为质数的情况
}
时间复杂度为。
2、短除法
当已经筛出每个数的最小质因数时,可以更加高效的分解质因数。
对于每个正整数,易证其最小质因数一定在其唯一分解式中。则将除以后,可迭代处理。边界条件为。
void decom(int n, int &m, int *p, int *c) {
m = 0;
for (; n != 1; n /= v[n])
if (p[m] == v[n]) // 重复,累计个数
++c[m];
else
p[++m] = v[n], c[m] = 1;
}
约数
定义
略。。。
算数基本定理的推论
对于一个正整数:
的约数个数为。
的约数和为
正约数集合
1、试除法
若且,则定有。即约数总是成对出现的。
因此,只需扫描中的数。对于每个数,判断其能否整除,若能整除,则将和同时加入的约数集合中。当时特判即可。
void calc(int n, int &m, int *d) {
m = 0;
for (int i = 1, size = sqrt(n); i <= size; ++i)
if (n % i == 0) {
d[++m] = i;
if (i != size) d[++m] = n / i;
}
}
时间复杂度为。
推论:一个正整数的约数个数上界为。
2、迭代法
求中每个数的约数集合,可以使用迭代法。
扫描每个数,并将其加入其所有范围内的倍数的约数集合中。
void calc(int n, vector<int> *d) {
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; j += i)
d[j].push_back(i);
}
时间复杂度为。
注:调和级数,其中为欧拉常数。
推论:中每个数的约数个数总和大约为。
约数和 & 约数个数和
除法分块
详见P2261 [CQOI2007]余数求和。
这是Capella的题解,比较详细。
数论函数
积性函数
线性筛
线性筛可以在线性的时间复杂度内求出一个积性函数的值。
首先要完全理解线性筛素数。
考虑到线性筛时会被筛去,
将分解,得到,
则一定有。
因为当时就已经break了。
而对于一个积性函数,要求,就必须能够快速求出以下函数值
- (为素数)
-
(为素数)
假设已经完成了上述函数值的计算,
欧拉函数
李煜东的《算法竞赛进阶指南》中讲解很详细,这里补充2条性质:
1、除了,;
2、当为奇数时,。
例题
P1390 公约数的和
P2158 [SDOI2008]仪仗队
莫比乌斯函数
略。。。
同余
详见李煜东的《算法竞赛进阶指南》。
剩余系
费马小定理
欧拉定理
反演
略。。。
卷积
略。。。