数论学习笔记
主要参考 OI-Note Chapter4.1 整除与同余 - 知乎
整除
若 为整数,则称 整除 ,记作 。若 和 的余数相等,则称 模 同余。
同余
关于同余,有以下命题等价:
- 和 是模 同余的。
- 存在某个整数 ,使 。
- 整除 。
由此可以轻易推出以下性质:
同余的基本性质
模运算基本性质
欧几里得辗转相除法(gcd)
给定整数 ,将它们的最大公因数记作 ,欧几里得辗转相除法便是用来求 的,因而简称 。
记 的公因数集合为 , 的公因数集合为 ,有 。证明:
设 , , ,所以 , 。
同样的,记 , , ,所以 , 。
综上, ,因而可以直接递归求解。时间复杂度为 。
边界:当 时, 。
code:
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
扩展欧几里得(exgcd)
由欧几里得辗转相除法,我们可以运用这个算法在求最大公因数的同时求出满足 的正整数解 。
对于边界条件 , 就是一组合法解(当然,此处 可以取任意数)。那么,设 ,且我们已经求出 满足 ,于是:
因此,满足 的 。
code:
int exgcd(int a, int b, int &x, int &y){
if(!b){
x = 1, y = 0;
return a;
}
int a1 = exgcd(b, a % b, x, y);
int x1 = y, y1 = x - a / b * y;
x = x1, y = y1;
return a1;
}
乘法逆元
对于一个数 ,若 ,那么我们称 的逆元为 ,记作 。在同余中,则是若 , 则 为 的逆元。通常,我们通过找到 的逆元来避免除法。给定 和 ,我们需要求出 满足 。
逆元唯一性定理:逆元若存在,则总是唯一的。
反证法。设有 使得 ,则有 ,且 , 则 ,矛盾。
逆元存在性定理:在模 下,当且仅当 时, 的逆元存在。后续补充证明。
事实上, 等价于 。由逆元存在性定理, 时, 。所以 , 。而若 ,则 ,所以令 , 就有 。因此这两者等价。我们就可以直接使用exgcd求出 在模 下的乘法逆元。不过有一个需要注意的是,使用exgcd求出来的逆元可能是负数,输出时加一个 (x % p + p) % p
就好啦。
线性求逆元
我们知道 。
设 ,就有:
同时乘以 得:
移项得:
即
又因为 比 小,我们就可以 求逆元了!
裴蜀定理
不定方程 有整数解 的充要条件是 。
证明:
当 时,我们可以先用exgcd求出 的解,然后同时乘以 即可得出解。
当 时,在该方程两边同时除以 ,发现方程左边为整数,右边为分数,则必定无解。
由此,我们就可以运用裴蜀定理直接证明逆元存在性定理了。
另外, 若 ,运用逆元可以得出 。这也是一条同余的性质。
虽然是个板子题,但是非常恶心。
无解情况裴蜀定理判定即可。
对于有解:
首先我们就可以通过exgcd求出满足 的一组整数解 ,记 。怎么由一组特解推出通解呢?
由观察可知,当 变大时, 会变小。因此设 为另外一组解 ( 为一个定值),我们通过改变 来求得每一组解。这个时候可能聪明的你会感觉到有些不对劲:为什么 和 的系数绝对值刚好都是 呢?就不能 吗? 又可以是什么定值呢?我们来证明一下:
不管,我们可以列出方程组:
可以解得 。也就是 需要满足这个等式才能构造出另外一组解。但还不够,我们需要 为最小的正整数 ,以此来通过调整系数 求出每一组解,要求 , 。所以 (此处详见下面的最大公倍数), 。同理 。我们就求出了 的最小正整数。
我们设另一组解为 ,则:
解得 。又由上面的 ,我们就会发现 ,所以 就好啦!
之后具体细节分类讨论即可(然后就调了一上午) 。推荐题解第一篇,感觉最简单易懂!(加 longlong, 不然就会在不知不觉中浪费1个小时QAQ)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mo 19260817
int a, b, c;
int exgcd(int a, int b, int &x, int &y){
if(!b){
x = 1, y = 0;
return a;
}
int a1 = exgcd(b, a % b, x, y);
int x1 = y, y1 = x - a / b * y;
x = x1, y = y1;
return a1;
}
signed main(){
// freopen("shuju.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int TN;
cin >> TN;
while(TN--){
cin >> a >> b >> c;
int x, y;
int gcd = exgcd(a, b, x, y);
x *= c / gcd, y *= c / gcd;
int p = b / gcd, q = a / gcd;
if(c % gcd) {
cout << -1 << endl;
continue;
}
int sl = ceil((1.0 - x) / p), sr = floor((y - 1.0) / q);
if(sl > sr) {
cout << x + sl * p << " " << y - sr * q;
}
else{
cout << sr - sl + 1 << " ";
cout << x + sl * p << " " << y - sr * q << " " << x + sr * p << " " << y - sl * q;
}
cout << endl;
}
return 0;
}
欧拉定理
当 时,
[[欧拉函数&欧拉定理]]
详细证明可见 欧拉函数 & 欧拉定理
证明:
我们构造一个数 ,只需使得 ,且 即可证明欧拉定理。设在模 下的剩余系中与 互质的数的集合为 ,令 ,这时 。又因为 , 所以集合 中每个数都与 互质,即 。那 。证毕。
由欧拉定理,我们能直接得出费马小定理,实际上欧拉定理就是费马小定理的推广。
最小公倍数
我们已经知道可以通过 求出 的最大公因数,那么有没有类似的快捷方法求出 的最大公倍数呢?
当然有!通过算术基本定理,我们将 表示成如下形式:
其中 属于 的所有质因数集合。
那么
而
于是
Lucas定理
前置知识:二项式定理
可以看看容斥定理及二项式反演
Lucas定理:
其中 是质数。
可以看成是 在 进制下各数码的组合乘积。
证明(不要被式子吓到啦,很简单的):
可以看成 中 项的系数,我们就尝试写一下:
因为 是质数,所以对于 , 在模 下均有逆元,
因为中间的系数在模 下为 ,在 中相加不会对模 后 项的系数有所影响,就可以直接忽略。于是我们继续:
发现这个式子要想凑出 那一项,因为左边只能提供 的倍数次项,右边只能提供模 的余数的项,所以对 的系数有贡献的项必须是是左边提供 的项, 右边提供 的项,所以 项的系数在模 下就是
证完!
素数的线性筛法
为了让一个合数只被标记一次,我们设法让一个合数只会被它的最小素因子标记一次。
vector<int> p;
int tag[N];
void init(int n){
tag[1] = 1;
for(int i = 2; i <= n; i ++){
if(!tag[i]) p.push_back(i);
for(auto j : p){
if(j * i > n) break;
tag[i * j] = 1;
if(i % j == 0) break;
//这里我们假设i = k*j, 下一个素数为g, g > j
//i*g = k*j*g, 因为k*g > i,所以i*g在之后循环到k*g时被标记,这里就不用标记了
}
}
}
整除分块
能够在 的时间内求出 。
正常肯定是 的,但是考虑一下如何加快。随便画一个反比例函数图像,会发现有对于每个 ,几乎都会有一段连续区间 的值是相同的。结论是,对于 所在的块,块的右端点是 。
另外一个结论,
int g(int n){
int ans = 0;
for(int l = 1, r; l <= n; l = r + 1){
r = n / (n / l);
ans += (r - l + 1) * n / l;
}
return ans;
}
威尔逊定理
为素数等价于
本文作者:yduck
本文链接:https://www.cnblogs.com/yduck/p/17799163.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步