一、质数
二、约数
三、欧拉函数
四、快速幂
五、扩展欧几里得算法
六、中国剩余定理
七、高斯消元
八、组合计数
九、容斥原理
十、简单博弈论
一、质数
质数:在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数,或者叫素数
(一)质数的判定——试除法
1、sqrt(x)
时间复杂度O(n½)
sqrt(x)这个函数执行的比较慢,所以不推荐
bool is_prime(int x)
{
// 1、质数大于1
if(x < 2) return false;
for(int i = 2; i <= sqrt(x); i++)
{
if(x % i == 0) // 2、只包含1和本身这两个约数
return false;
}
return true;
}
2、推荐此方法i ≤ n / i
核心:i <= x / i可以有效提高性能
bool is_prime(int x)
{
// 质数大于1
if(x < 2) return false;
for(int i = 2; i <= x / i; i++)
{
if(x % i == 0)
return false;
}
return true;
}
(二)分解质因数——试除法
时间复杂度O(n½)
思想:从小到大尝试n的所有因数
解释代码里面x % i == 0的i一定是质数,这里可以怎么理解:i = c,如果c是合数且满足x % c == 0,那么一定存在比它小的另外两个数a,b,满足c = a * b。但是由于i是从2开始递增的,所以早在i递增到a或者b(这两个比c小的数的时候),x就已经被整除了,所以根本到不了i = c这个地方。来个实际的例子,x = 16,x就会在i = 2的时候被整除完,根本不会到所谓的i = 4。也就是说到i = c这个地方的时候x已经不存在2~i-1的任何质因子了
代码:
//试除法分解质因数
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
{
if (x % i == 0) // i一定是质数,具体解释看上面蓝色字体
{
int s = 0; // s代表x可以被i整除的次数
while (x % i == 0) x /= i, s ++ ;
printf("%d %d\n", i, s); // 这里可以使用vector<int, int> res;来记录
}
}
if (x > 1) printf("%d %d\n", x, 1); // 输出那个大于根号x的最大质因子
}
(三)筛法求素数
1、朴素筛法求素数
算法核心:把每个质数的所有倍数筛掉,所有剩下的数就是质数
调和级数:1 + 1/2 + 1/3 +…+ 1/n = lnn + c(c欧拉常数=0.577)
算法时间复杂度:内部循环总次数是n/2,n/3,n/4…1,累加得n(1/2 + 1/3 + 1/4 +…+1/n)=nlnn=nlogn
模板代码:
const int N = 1000010;
int primes[N], cnt; // primes[]记录素数,cnt记录素数的个数,作为primes[]的索引
bool st[N]; // st[x]存储x是否被筛掉 true 说明被筛过
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) //i如果没被筛过,说明i是质数,将i加入质数数组中
primes[cnt ++ ] = i;
//接下来对该质数的所有倍数进行筛选
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
缺点:同一个合数可能会被重复筛,比如8,会被2,4同时筛一次
2、埃式筛法O(nloglogn)
算法核心:把每个质数的所有倍数筛掉
质数定理:1~n中有n/logn个质数
实际算法时间复杂度:当数据不是足够大时与O(n)接近
代码:
const int N = 1000010;
int primes[N], cnt; // primes[]记录素数,cnt记录素数的个数,作为primes[]的索引
bool st[N]; // st[x]存储x是否被筛掉 true 说明被筛过
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) //st如果是true 说明被筛过,那么它的倍数肯定被筛过,所以直接跳过
{
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i) //接下来对该质数的所有倍数进行筛选
st[j] = true;
}
}
}
3、线性筛法——每次用这个!
思路:把每一个合数用他的质因子筛掉就可以了,x只会被它的最小质因数筛去,即每个数字只被筛选一次,因此是线性的n。
算法时间复杂杂度:因为每个数只被它的最小质因子筛过一次,因此为O(n)
int primes[N], cnt; // primes[]记录素数,cnt记录素数的个数,作为primes[]的索引
bool st[N]; // st[x]存储x是否被筛掉 true 说明被筛过
void get_prime(int x)
{
for(int i = 2 ; i <= x ; i++)
{
if(!st[i]) primes[cnt ++] = i;
// 现在是从小到大筛掉所有合数,每个合数只会被它的最小质因子筛一次。所以时间复杂度是O(n)
for(int j = 0 ; primes[j] <= x / i ; j++)
{
st[primes[j] * i] = true; //筛去primes[j]的倍数
/*
针对st[primes[j] * i] = true;本质上分两种情况
1.i % primes[j] == 0, 因为primes[j]是顺序遍历,因此当第一次模为零时,primes[j]一定为i的最小质因子,
primes[j]也一定为primes[j] * i的最小质因子
2.i % primes[j] != 0, 同样因为primes[j]是顺序遍历,primes[j]一定小于i的所有质因子所以primes[j]也
一定为primes[j] * i最小质因子
*/
if(i % primes[j] == 0) break; // 这句话成立的时候说明,primes[j]一定是i的最小质因子
}
}
}
这里以9为例子,当i取到3的时候,并且primes[j]取到3的时候,此时就会被筛掉,而且这个primes[j]=3是9的最小质因子
以24为例子,当i取到12的时候,并且primes[j]取到2的时候,此时就会被筛掉,而且这个primes[j]=2是9的最小质因子
x=24的时候,筛掉数的顺序是
4___i = 2 primes[] = 2
6___i = 3 primes[] = 2
9___i = 3 primes[] = 3
8___i = 4 primes[] = 2
10__i = 5 primes[] = 2
15__i = 5 primes[] = 2
12__i = 6 primes[] = 2
14__i = 7 primes[] = 2
21__i = 7 primes[] = 3
16__i = 8 primes[] = 2
18__i = 9 primes[] = 2
20__i = 10 primes[] = 2
22__i = 11 primes[] = 2
24__i = 12 primes[] = 2
4、把数x分解成质因数的乘积形式
eg:360 = 23 * 35 * 51
代码:
// 返回的是map,prime[a]=b 的含义是a的b次幂
unordered_map<int , int> fenjie(int x)
{
unordered_map<int , int> primes; //利用哈希表存储所有的指数和底数,prime[a]=b 的含义是a的b次幂
for(int i = 2 ; i <= x / i; i++) // 求x的质因数
{
while(x % i == 0)
{
x /= i;
primes[i] ++; // x对应的这个质因数i,i的指数加一
}
}
if(x > 1) primes[x] ++; // 如果x大于1,说明现在的x是原始x的比较大的一个质因数
return primes;
}
tips:
这里的primes可以使用(auto t : primes)迭代,然后用t.first,t.second来访问
二、约数
约数,又称因数。整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数。
(1)试除法求所有约数O(n½)
思路:从小到大枚举较小的约数即可
// 求x的所有约数
vector<int> get_divisors(int x)
{
vector<int> res;
for(int i = 1 ; i <= x / i ; i++)
{
if(x % i == 0) // i是x的一个约数,且x / i 也是x的一个约数——i * (x / i) = x
{
res.push_back(i);
if(i != x / i) res.push_back(x / i); // 避免i和x / i 相同,重复加入
}
}
sort(res.begin() , res.end());
return res;
}
(2)求某个数的约数个数

算法思想:
基于算数基本定理:
N = p1α1 * p2α2 * … * pkαk
N的任意一项约数d可以写成 d = p1β1+p2β2+…+pkβk (这里β是满足在0—α的)
不同的β数组,组合而成的约数d是不同(即因式分解不同)
同理不同的a数组组合而成的N也是不同的,
而α1有α1+1种选法,α2有α2+1种选法…αk有αk+1种选法
因此约数个数:(α1+1)(α1+1)(α3+1)…(αk+1)
如果看不懂这里的证明,可以去看b站这个视频,链接如下公式推导:约数个数和约数之和
题目描述
给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对1e9+7取模。
代码:
#include <bits/stdc++.h>
using namespace std

本文介绍了质数的判定与分解质因数的算法,包括试除法和筛法,并详细讲解了约数的计算方法。此外,还探讨了欧拉函数的定义、计算公式及其应用,以及快速幂在求模运算中的高效性。
最低0.47元/天 解锁文章
1280

被折叠的 条评论
为什么被折叠?



