数学知识(一)
温馨提示:本篇含有大量 公式,可能加载较慢!
一. 质数
定义
若一个正整数的因数只有
质数分布定理
用人话来说就是当
质数的判定
如何判定一个正整数
试除法
从定义出发,枚举
bool check(int n) {
if(n < 2) return false;
for(int i = 2; i <= n / i; i++)
if(n % i == 0) return false;
return true;
}
分解质因数
算数基本定理
内容:
任何一个大于
在分解质因数时也可以用试除法,枚举
for(int i = 2; i <= n / i; i++) {
int s = 0;
while(n % i == 0) n /= i, s++; //除尽
if(s > 0) printf("%d %d\n", i, s);
}
if(n > 1) printf("%d %d\n", n, 1); //可能会剩下一个比较大的质因子
质数筛
1. 埃氏筛
思路:
把找到的质数的所有不大于
但是,同一个数会被它的不同质因子都筛一遍,效率有所降低。
时间复杂度:
代码:
void ai(int n) {
for(int i = 2; i <= n; i++) {
if(!st[i]) {
primes[++tt] = i;
for(int j = 2; i * j <= n; i++) st[i * j] = true;
}
}
}
2. 欧拉筛(线性筛)
思路:
在埃氏筛的基础上进行了优化,保证每个合数只会被它的最小质因子筛掉,时间复杂度有所降低。
时间复杂度:
代码:
void ol(int n) {
for(int i = 2; i <= n; i++) {
if(!st[i]) primes[++tt] = i;
for(int j = 1; j <= tt && i * primes[j] <= n; j++) {
st[i * primes[j]] = true;
if(i % primes[j] == 0) break; //确保primes[j]一定是i的最小质因子,则primes[j]一定也是i * primes[j]的最小质因子,这样就确保了每个合数只会被它的最小质因子筛掉
}
}
}
二. 因数
对于一个数
对于每一个因数
由排列组合知识可知,
约数之和
根据等比数列求和公式可得:
试除法分解因数
因为一个数
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n;
vector <int> get(int x) {
vector <int> res;
for(int i = 1; i <= x / i; i++) {
if(x % i == 0) {
res.push_back(i);
if(i != x / i) res.push_back(x / i);
}
}
sort(res.begin(), res.end());
return res;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
vector <int> ans = get(x);
for(auto i : ans) printf("%d ", i);
puts("");
}
return 0;
}
最大公因数
定义:
若
最小公倍数
定义:
若
几个有意思的定理
-
。 -
的所有公因数都是最大公因数的因数,所有公倍数都是最小公倍数的倍数。
证明:
考虑将
分解质因数,并将质因数对齐,得: 再将
和 分解质因数,得: 注意!这里的
可能为 !(因为可能不含此质因子) 根据定义可知:
对于每个质因子
来看, 与 相乘即为: 所以总的乘起来就是
。 证明完毕。
由
若直接朴素计算
还有更快的方法吗?
九章算术 · 更相减损术
内容:
证明:
对于
的任意公因数 ,由于 ,所以 。因此, 也是 的公因数。所以 的公因数集合与 的公因数集合相同,那么最大公因数自然也就相同。对与 同理。 证明完毕。
这种方法当
但是这个方法可以扩展到
欧几里得算法
我们发现上一种方法一次一次地减效率太低了,干脆一次就减完,即取模。
欧几里得算法就是在这个方面进行了优化。
内容:
证明:
和上一种方法如出一辙。
若
,则上式显然成立。 若
,不妨设 ,其中 。显然 。 对于
的任意公约数 ,由于 ,所以 ,即 。因此 也是 的公因数。所以 的公因数集合和 的公因数集合相同,最大公约数自然也就相等了。 证明完毕。
每次我们将
-
当
时, ,就变成下面情况 或 。 -
当
时, 进一步分析就会发现,括号中的第二个元素至少减少了一半。 -
当
,括号中的第二个元素 一定小于 也至少减少了一半。
综上所述,每次迭代第二个元素都会至少减半,所以时间复杂度是
一般的,对于多个数
直接上代码:
int gcd(int x, int y) { //最大公因数
if(y == 0) return x;
return gcd(y, x % y);
}
int lcm(int x, int y) { //最小公倍数
return x * y / gcd(x, y);
}
欧拉函数
定义:
欧拉函数就是指对于
对于
, , 来讲,满足 (即 为其质因子)
证明在这里
套公式代码:(时间复杂度:
#include <iostream>
using namespace std;
int n;
int main() {
scanf("%d", &n);
while(n--) {
int x;
scanf("%d", &x);
int res = x;
for(int i = 2; i <= x / i; i++) { //分解质因数
if(x % i == 0) {
res = res / i * (i - 1);
while(x % i == 0) x /= i;
}
}
if(x > 1) res = res / x * (x - 1);
printf("%d\n", res);
}
return 0; //直接套公式也没什么好说的
}
若要求
其实可以在线性筛质数时顺便求欧拉函数。
首先需要了解积性函数。
定义:
基本性质:
若
证明:
将
分解质因数,对每个质因子都使用积性函数的定义,此性质显然成立。
接着我们需要欧拉函数的几个性质:
-
欧拉函数是积性函数。
-
设
为质数,若 且 ,则 。 -
设
为质数,若 且 ,则 。
证明:
性质
要证欧拉函数是积性函数,即证:有
互质且有 。 因为
,所以 的质因数一定没有一个相同,将 分解质因数。 设
则
而
。 性质
得证。
性质
若
且 ,则 拥有相同的质因子,只是指数不同罢了,但是欧拉函数的计算只看有没有这个质因子而不关心指数。设 的质因子都为 ,则 两者相除,商为
。
性质得证。
性质
若
但 ,则 互质,根据性质 可得 ,而 ,所以 。 性质
得证。
综上所述,我们可以把上述性质与线性筛法结合起来。
具体地,每个合数
时间复杂度为
void get_eulers() {
phi[1] = 1;
for(int i = 2; i <= n; i++) {
if(!st[i]) {
prime[++cnt] = i;
phi[i] = i - 1; //若是质数就直接求出欧拉函数
}
for(int j = 1; j <= cnt && prime[j] <= n / i; j++) {
st[i * prime[j]] = true;
if(i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j]; //此时 primes[j] 是 i 的最小质因子,即此时满足性质 2
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1); //否则满足性质 3
}
}
}
三. 同余
概念:
数学上,同余(
同余运算的常见性质
证明:
对于性质
根据定义,设
,所以 ,所以 。 证毕。
对于性质
根据性质
得: ,所以 ,即 证毕。
对于性质
根据性质
得: ,因为 ,所以 ,又因为 ,所以 ,即 。 证毕。
基本知识 —— 关于
-
同余类/剩余类
由对于模
可知,模
如果模
-
完全剩余系:
从模
显然,完系中的
-
最小非负完全剩余系
最简单的模
-
简化剩余系
简化剩余系(
有无穷多个简化剩余系。例如,模
此时欧拉函数的真正定义才浮出水面:
欧拉函数(
)是对于任意正整数其最小非负完系的个数,用 来表示,即 当我们用
来表示模 的最小非负完系时,显然 是成立的。而 即表示
的个数,此时
费马小定理
内容:
若
是质数,则对于任意整数 ,有 。
欧拉定理
内容:
若正整数
,则 ,其中 为欧拉函数。
证明:
设
是 的一个缩系,且 ,换句话说,这些数都是 中与 互质的数,且两两不同。 又
互质。
也都与 互质,且两两不同。
集合 和集合 在模 的条件下完全等价,即: 这里就要用到一个同余运算的法则了:若
,且 ,那么 。 证明:根据同余的定义,原式可以写成
,整理得: 。
需要含有因子 。 又
。
即
,证明完毕。 回到原题,由于
都与 互质,所以 也与 互质,则可以消掉,得到 。
那么当模数
裴蜀定理
内容:
对于任意整数
证明:
第一点是显然的,因为
,所以 。 对于第二点:
当
时,显然当 时原式成立。 当
时,则 。假设存在一对整数 ,满足 ,因为 ,所以进一步令 ,就得到了 。 接着对欧几里得算法的递归过程使用数学归纳法,可知第二点成立,也就是顺着欧几里得算法递归,最终会把
消成 。 证明完毕。
P4549 【模板】裴蜀定理
这道题就考察对裴蜀定理的思考深度了。
先看两个数的情况,假如要最小化
推广到
事实上,由于
同时,裴蜀定理给出了整数
int exgcd(int a, int b, int &x, int &y) {
if(b == 0) {x = 1, y = 0; return a;}
int xx, yy;
int d = exgcd(b, a % b, xx, yy);
x = yy;
y = xx - (a / b) * yy;
return d;
}
注意
对于更一般的方程
那么我们该怎么表示它的通解呢?
设
综上所述,
推广到一般情况
线性同余方程
定义:
形式化定义:形如:
求解
根据同余运算的法则可得:
在有解时,先用扩展欧几里得算法求出方程
乘法逆元
定义:
若整数
简单说来,逆元就是人们发现同余运算中有的时候会出现除法,而除法的计算又是比较复杂的,所以就想了一个办法将它转化为乘法,于是逆元应运而生。
因为
当
所以,当模数
意思是若模数时质数,就可以用快速幂来求逆元。
那么如果只保证
P3811 【模板】模意义下的乘法逆元
若用上述方法一个一个地求逆元,时间复杂度为 (其实快速幂加上巴雷特模乘能卡过)。
其实要快速求出
设
所以有:
时刻铭记我们的目标是
两边同乘上
即
特别地,当
然后就可以开始快乐地线性递推了。
inv[1] = 1; //注意初始化
for(int i = 2; i <= n; i++)
inv[i] = p - (p / i) * inv[p % i] % p;
P5431 【模板】模意义下的乘法逆元 2
若要求
注:以下所有运算都是在
设这
那么它们的逆元分别为
计算这
有递推式:
而
所以我们可以先朴素求出
sum[0] = 1;
for(int i = 1; i <= n; i++) {
a[i] = read<ll>();
sum[i] = (sum[i - 1] * a[i]) % p;
}
ll x0, y0;
exgcd(sum[n], p, x0, y0);
inv_sum[n] = (x0 % p + p) % p;
for(int i = n - 1; i; i--)
inv_sum[i] = (inv_sum[i + 1] * a[i + 1]) % p;
ll tmp = k, res = 0;
for(int i = 1; i <= n; i++) {
res = (res + tmp * inv_sum[i] % p * sum[i - 1] % p) % p;
tmp = tmp * k % p;
}
中国剩余定理
内容:
设
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析