数论大杂烩
质数和约数#
质数是指除了
和它本身之外没有其他因数的自然数。
质数判定#
判定单个自然数是否为质数,可以使用试除法,在这里不多描述。
bool is_prime(int n){
if(n < 2) return 0; // 如果n小于2,不是质数,返回0
for(int i = 2; i <= n / i; i++) // 从2开始逐个尝试除数i,直到i大于n的平方根
if(n % i == 0) return 0; // 如果i能整除n,说明n不是质数,返回0
return 1; // 如果没有找到能整除n的除数,说明n是质数,返回1
}
此算法复杂度为
而接下来介绍判断
Eratosthenes筛法 (埃氏筛法)
我们知道一个合数一定可以分解为
那么我们就可以枚举
但是我们会发现,如果使用这样的埃氏筛法,有一些数字会被标记多次,如
所以我们可以做出一个小小的优化:
对于素数
还可以发现,在枚举的过程中,每次筛完后剩下的区间内第一个数一定是质数,原因同上。
所以枚举质数时不需要从
此算法时间复杂度为
bool p[MAXN]; // 布尔数组,用于标记数字是否为合数
p[0] = p[1] = 1; // 将0和1标记为合数,因为它们不是质数
for(int i = 2; i <= n; i++){
if(p[i]) continue; // 如果当前数已经被标记为合数,则跳过,因为它不是质数
for(int j = i; i * j <= n; j++){
p[i * j] = 1; // 将当前数的倍数(p * s)标记为合数
}
}
线性筛法
尽管上面的埃氏筛法已经经过优化,减少了重复枚举的次数,可是合数还是会被重复枚举。
而这里的线性筛法,顾名思义,它的时间复杂度是
怎么做到的?
线性筛法每个合数只被它的最小质因数(
依次考虑
如果
否则利用
注意,筛的过程中要确保
bool p[MAXN]; // 布尔数组,用于标记数字是否为合数
int cnt = 0; // 计数器cnt
p[0] = p[1] = 1; // 特判,将0和1标记
for(int i = 2; i <= n; i++){
if(!p[i]) pri[++cnt] = i; // 如果当前数字i是质数,则将其加入质数数组pri,并增加计数器cnt
for(int j = 1;pri[j] <= n / i; j ++){
p[i * pri[j]] = 1; // 将当前数字i与质数数组中的质数相乘得到的倍数标记为合数
if(i % pri[j] == 0) break; // 如果i能被pri[j]整除,则跳出内层循环,避免重复标记
}
}
练习1:Prime Distance
简要题面
-
给定两个整数
, 求 中相邻的两个差最大的质数和相邻的两个差最小的质数。 -
解题思路
由于数据范围很大,无法生成
使用筛法求出
对于每个质数
将筛出的质数进行相邻两两比较,找出答案即可。
分解质因数/子#
对于以下问题:
给定一个正整数
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,i,j;
cin>>n;
for(i = 2;i <= n / i;i ++){ // 从 2 枚举到 sqrt(n)
if(n % i == 0){ // 如果 i 能整除 n,说明 i 是 n 的一个质因子
cout<<n / i<<endl;
break; // 找到 ans 退出。
}
}
return 0;
}
此程序的原理,在这里不多赘述,前文已经写过了原因[1]。
此算法复杂度为
同样对于以下问题:
给定一个正整数
vector<int> breakdown(int N) {
vector<int> result;
for (int i = 2; i * i <= N; i++) {
if (N % i == 0) { // 如果 i 能够整除 N,说明 i 为 N 的一个质因子。
while (N % i == 0) N /= i;
result.push_back(i);
}
}
if (N != 1) { // 说明再经过操作之后 N 留下了一个素数
result.push_back(N);
}
return result;
}
此算法复杂度为
算术基本定理#
若整数
练习2:细胞分裂
简要题面
他希望能选择一种细胞进行培养,使得细胞的个数能够均分为
求选择哪种细胞可以使得实验的开始时间最早。
如果无论选择哪种细胞都不能满足要求,则输出
解题思路
我们需要使
若质数
若都无法满足则答案为
约数#
约数的基本性质
若整数
则称
若正整数
且满足
求正约数集合
想求一个自然数的正约数集合,可以使用试除法。
一个自然数的
int compute_SOPA(int n){
int a[MAXN],tmp = 1; // 初始化
for(int i = 1;i <= n / i;i ++){
if(n % i == 0){ // 判断因数
a[tmp ++] = i; // 加入集合
if(n / i != i)a[tmp ++] = n / i; // 根据除法的性质,用一个因数求出另一个因数,减少循环
}
}
return a;
} // 与判断质数相反QWQ
此算法的时间复杂度为
如果想求
// 作者太懒没写
求正约数个数
前文已经给出了公式[3],此处不再赘述。
unordered_map<int,int> zjs(int n){ // 分解质因子,但是使用哈希表存储质因子
unordered_map<int,int> pri;
for(int i = 2;i <= n / i;i ++){
while(n % i == 0)
pri[i] ++,n /= i;
}
if(n > 1)pri[n] ++;
return pri;
}
unordered_map<int,int> pri = zjs(n);
long long sum = 1; // 初始化 sum 为 1
for(auto it:pri) // 遍历 pri
sum = sum * (it.second + 1); // it.second 即获取 it 指向元素的 vluae
求正约数和
同上,前文给出公式[4],此处也不再赘述。
unordered_map<int,int> zjs(int n){ // 分解质因子,但是使用哈希表存储质因子
unordered_map<int,int> pri;
for(int i = 2;i <= n / i;i ++){
while(n % i == 0)
pri[i] ++,n /= i;
}
if(n > 1)pri[n] ++;
return pri;
}
unordered_map<int, int> pri = zjs(n);
long long sum = 1;
for (auto it:pri) { // 遍历 pri
int p = it.first,a = it.second; // p 为 it 指向元素的键(key),a 为 it 指向元素的值(value)
long long t = 1;
while (a --)
t = (t * p + 1);
sum *= t;
}
练习3:反素数
简要题面
对于任何正整数
如果某个正整数满足: 对于任意的
求不超过
解题思路
综上,DFS
即可。
最大公约数和最小公倍数#
能使
能使
最大公约数与最小公倍数的性质
若
若
求解最大公约数
这里有两种算法。
- 更相减损术
不详细介绍,只给出公式:
- 欧几里得算法(碾转相除法)
先放公式:
算法很简单,通俗来讲,就是用
直到余数为于
例如求
按照上面的算法,我们简单的可以写出一个递归函数。
int gcd(int a, int b){
if(b == 0)return a;
return gcd(b,a % b);
}
但是递归是很慢的,我们可以优化出一个循环版本的。
int gcd2(int a,int b){
while(b > 0){
int t = a;
a = b;
b = t % b;
}
return a;
}
此算法的时间复杂度为
求解最小公倍数
求
可以写出代码:
a / gcd(a,b) * b;
这里有个细节,为了防止 a * b
爆 long long
,先除后乘,结果不变。
互质与欧拉函数#
首先,什么是互质?
若
欧拉函数的特性
-
, 中与 互质数的和为 。 -
若
互质,则 。积性函数:如果
互质时,有 ,那么称函数 为积性函数。 -
若
是积极性函数,且在算术基本定理中 则 。 -
设
为质数,若 且 则 。 -
设
为质数,若 且 则 。 -
。练习4:仪仗队
简要题意
君站在一个由学生组成的 的方阵的左后方,如· · · · · · · · · · · · · · · * · · · · C君站在 * 处 · 是学生所站位置
君希望知道他能看到的学生数。解题思路
除了
外, 处的学生能被看到,需满足:因此,答案为
。线性筛求欧拉函数
for(int i=2; i<=n; ++i) { // 从2到n遍历 if(!p[i]) { pri[++cnt] = i; // pri数组存储质数 phi[i] = i-1; // 欧拉函数的初始值 } for(int j=1; j<=cnt && i*pri[j]<=n; ++j) { // 遍历质数数组pri,直到i*pri[j]超过n p[i*pri[j]] = true; // i*pri[j]不是质数(标记为true) if(i % pri[j] == 0) { phi[i*pri[j]] = phi[i] * pri[j]; // 若i是pri[j]的倍数,欧拉函数的计算 break; // 跳出循环 } else { phi[i*pri[j]] = phi[i] * (pri[j] - 1); // 欧拉函数的计算 } } }
同余#
逆元#
对于一个线性同余方程
扩展欧几里得求单个逆元
将线性同余方程
然后利用扩展欧几里得求得解。
void Exgcd(ll a,ll b,ll &x,ll &y) {
if (!b) x = 1, y = 0;
else Exgcd(b, a % b, y, x), y -= a / b * x;
}
int main() {
ll x, y;
Exgcd(a, p, x, y);
x = (x % p + p) % p;
printf("%d\n",x); //x是a在mod p下的逆元
}
线性求解多个连续逆元
首先,我们可以确定
然后设
再将这个式子放到
然后乘上
这样,我们就可以用递推的方式求出
inv[1] = 1;
for(int i = 2; i <= n;i ++)
inv[i] = (p - p / i) * inv[p % i] % p;
高斯消元#
简单容斥原理#
概率与数学期望#
PS:因为后面的太难,作者还不会😓,到时候慢慢更新吧!
作者:ManGo_Mouse
出处:https://www.cnblogs.com/mangofantasy/p/17553418.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际 CC BY-NC-SA 4.0」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】