数论及其相关 学习笔记
数论及其相关 学习笔记
todo
- 威尔逊定理 原根 二次剩余,这些另开文章
- 积性函数与筛子相关
- 数论分块
目录
-
前置知识与符号定义
-
素数筛
-
裴蜀定理
-
(扩展)欧几里得算法
-
同余方程
-
费马小定理
-
(扩展)欧拉定理
-
模意义下的乘法逆元
-
(扩展)卢卡斯定理
-
(扩展)中国剩余定理
0.前置知识与符号定义
在无特殊说明的情况下,所有数字均
0.0 唯一分解定理
对于任意正整数
其中
这个定理的意思是:每一个正整数都可以分解成若干不同素数的乘积。
0.1. 素数
对于一个数
0.2. 最大公因数
定义
设两数分别为
由于:
我们有:
0.3. 整除符号
定义
因为我在给 whk 的同学讲的时候手误把定义敲反了,所以一个等价定义是:
若
0.4. 模运算
定义
特殊的,当输出一个数
对 取模的结果时,请使用 cout<<(k%p+p)%p
而非cout<<k%p
,这可能导致一些符号错误。
有以下性质:
证明显然。
见“模意义下的乘法逆元”
0.5. 同余
若
有以下性质:
这是同余的一个重要基本性质。
由以上性质,有以下三个推论:
值得注意的是,等式的基本性质中加减乘三个运算一般在同余中成立,而除法需要特殊讨论,如下式。
若
证明:
并且,若
证明:
若有整数
使得 ,则
这是部分题目中的重要性质。
0.6. 快速幂
和数论没一点关系,放个板子。
https://www.luogu.com.cn/problem/P1226
对于快速幂的问题定义如下:给定两个整数
对于此类问题,暴力时间复杂度
我们不妨将指数采用二进制表述,设指数的二进制串为
若
其时间复杂度为
代码:
int m;
int qpow(int a,int b)
{
int ans=1,tmp=a;
while(b!=0)
{
if(b&1)
{
ans=(ans%m*tmp%m)%m;
}
tmp=tmp*tmp%m;
b>>=1;
}
return ans;
}
0.7. 两道奇妙例题
-
对于
,证明 。组合意义保平安!
考虑以下问题情景,将
个人分成 组,每组 人,求方案总数。显然将
个人的全排列的方案数为 ,而在每组内不需要考虑元素的先后顺序,所以多算了 种方案。而这 组本身也不需要考虑先后顺序,所以又多算了 种方案。所以这个问题的答案即为
,显然为整数,证毕。 -
对于
,证明 。鸽巢原理的奇特应用。
等价于证明
,因式分解可得 。变换形式,得
。由鸽巢原理,注意到以下两条基本性质:
- 连续的
个数必定是 的倍数。 - 连续的
个数必定是 的倍数。
由此,设
。上式即为
。化简得
,显然是 的倍数,证毕。 - 连续的
1. 素数筛
注:也可以筛一些其他东西。
1.1. 埃氏筛
对于素数筛问题的模板题
埃氏筛的思想如下,若我们需要筛出小于等于
可以证明,其时间复杂度为
以下代码实现筛出
void prime(int n)
{
int t=0;
for(int i=2;i<=n;i++)
isprime[i]=1;
for(int i=2;i<=n;i++)
{
if(isprime[i])
{
prime[++t]=i;
if(i*i<=n)
for(int j=i*i;j<=n;j+=i)
isprime[j]=0;
}
}
}
1.2.欧拉筛
欧拉筛模板题。
埃氏筛的时间复杂度为
欧拉筛的时间复杂度为
对于欧拉筛,每一个数会且仅会被筛到一次。
其做法是对于一个数
容易证明其正确性,若
附代码:
void prime(int n)
{
int t=0;
for(int i=2;i<=n;i++)
{
if(!chck[i])
pri[++t]=i;
for(int j=1;i*pri[j]<=n&&j<=t;j++)
{
chck[i*pri[j]]=1;
if(i%pri[j]==0)
break;
}
}
}
1.3. Miller Rabin
原理:基于费马小定理
给定若干组
若
证明:
综合以上定理,可以找到
-
令
,计算 ,若 ,则通过。 -
否则设
,若 ,则一定是合数。 -
若
不为 则为合数。 -
否则进行下一组测试。
-
所有测试通过即为素数。
bool miller_rubin(ll a,ll n)
{
ll s=n-1,r=0;
while((s&1)==0){
s>>=1;r++;
}
ll k=qmod(a,s,n);
if(k==1) return true;
for(int i=0;i<r;i++,k=k*k%n){
if(k==n-1) return true;
}
return false;
}
bool is_prime(ll n)
{
ll times =_;
ll prime[]={_};
for(int i=0;i<times;i++){
//miller_rubin();
}
}
2. 裴蜀定理
裴蜀定理的描述如下:
对于整数
有以下推论:
-
对于整数
( 中至少有一个不为 ),方程 ,则 (逆定理)。 -
对于一个数列
( 中元素不全为 ), 一定有解。
下文给出了一个构造性证明。
3. 扩展欧几里得算法(exgcd)
3.1. 辗转相除法(欧几里得算法)
辗转相除法可以用来求解最大公因数等问题。
其主要算法流程如下:
-
给定两数
,我们认为 。 -
若
返回 。否则递归求解
算法正确性证明如下:
首先证明
我们设
不难发现
逆命题容易证明。
Q.E.D
代码如下:
int gcd(int a,int b)
{
if(b==0) return a;
else return gcd(b,a%b);
}
对于两整数
可以从唯一分解定理的角度进行证明,即:
由此,有:
int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
使用 <algorithm>
库中的 __gcd()
函数也可。
注意 <numeric>
库中的 std::gcd
和 std::lcm
仅在 C++ 17 及以上标准中可用。
3.2 扩展欧几里得算法
3.2.1. exgcd 求不定方程特解
扩展欧几里得算法(exgcd)常用于这样一类问题:给定一个不定方程
即:求
我们可以尝试将问题转化为 4.1 中的欧几里得算法问题,即为 exgcd 算法。
与欧几里得法相似的,我们可以将
特殊的,exgcd 中
由欧几里得算法:
假设
所以
由定义,
所以我们只需要递归到
于是我们求出了不定方程
附代码:
int exgcd(int a,int b,int &x,int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int h = exgcd(b, a % b, y, x);
y -= a / b * x;
return h;
}
3.2.2. exgcd 求不定方程通解
假设我们已经知道了不定方程
我们不妨设
由最小公倍数性质:
带回原式,得:
所以,不定方程
3.2.3 不定方程的无解情况
这与裴蜀定理所讨论的情况不同。
对于不定方程
证明如下:由最大公因数定义,
4. 同余方程
同余方程模板题。
同余方程指的是形如
对于这种方程,我们可以将其转化为上文的不定方程
为什么?
所以
特殊的,与不定方程类似,同余方程同样可能无解,这发生在
对于不定方程
5. 费马小定理
费马小定理的描述如下:若
费马小定理是欧拉定理的一个推论,将在下文证明其正确性。
一个简单地应用:若
另一个不简单的应用是 Miller-Rabbin 算法。
6.欧拉定理
6.1 欧拉定理
定义
欧拉定理如下:
若
证明如下:
引理:
为 的一个简化剩余系,且 则 也为 的一个简化剩余系。 证明:
,有 。 所以
,即该元素仍存在于剩余系中。
原命题等价于:
两遍同时约掉
Q.E.D.
6.2. 扩展欧拉定理
考虑没有对
结论背过就行。
7. 模意义下的乘法逆元
所以我们有必要先知道什么是模意义下的乘法逆元(本文不讨论其它逆元,下文逆元皆指“模意义下的乘法逆元”)。
给定整数
如果抛弃模意义,乘法逆元其实就是取其倒数。
在下文中可能用
7.1. exgcd求逆元
逆元模板题。
显然可以用 4. 中提到的同余方程求解。
需要注意的是,由于不定方程可能无解,所以逆元只存在于
值得注意的是,这里仅要求
互质,而费马小定理还要求 一定为质数。
代码:
void exgcd(int a, int b, int &x, int &y)
{
if (!b)
x=1,y=0;
else
exgcd(b,a%b,y,x),y-= a/b*x;
}
exgcd(a,p,x,y);//a 在 mod p 下的逆元为 x。
7.2. 费马小定理求逆元
由费马小定理,
而逆元要求的式子是
不难发现
求一个数的逆元的时间复杂度
7.3. 线性求逆元
如果我们需要求
可以在线性时间复杂度求解该问题。
显然,对于任意的
我们可以递归的求解
设
将其代回模
两遍同乘
移项,得:
即:
我们可以发现,
特殊的,当
时 未定义,这是由于在 和 不互质时,没有逆元导致的,所以我们一般会将 设置为大质数,以防止这种情况的发生,这也是部分数论题的模数为大质数的原因。
代码:
inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i]%p;
}
我们使用 p-p/i
来避免出现负数。
7.4. 线性求任意 个数逆元
时间复杂度要求
这与 7.3 没有关系。
设
显然,有
有
代码:
s[0]=1;
for(int i=1;i<=n;i++)
s[i]=s[i-1]*a[i]%p;
invs[n]=qpow(s[n],p-2);//请注意题目所给是否是质数
for(int i=n;i>=1;i--)
invs[i-1]=invs[i]*a[i]%p;
for(int i=1;i<=n;i++)
inva[i]=invs[i]*s[i-1]%p;
8.卢卡斯定理
8.1 模数为质数的情况
卢卡斯定理可以解决一些大组合数取模问题。
对于质数
在此不做证明。
值得注意的,
int solve(int n, int m) {
if (m == 0) {
return 1;
}
else {
return solve(n / p, m / p) % p * C(n % p, m % p) % p;
}
}
8.2 exLucas
需先阅读 9.1。
事实上,
由于
问题转化为求解
展开,得:
我们无法直接计算
其中,
由于两数互质是存在逆元的充分必要条件,上式的分母一定存在模
即我们将问题进一步转化为求:
在这个式子中,我们考虑
观察最后的这个式子,它由三部分组成:
的幂,其次数是 。 它和第一部分共同构成了与 互质部分的乘积。 中,与 不互质部分的乘积,首先是每 个一组,共 组,之后是无法被完整归到组内的剩余部分。
形式化的,
由于
由于
现在我们考虑如何求解
由于
将
将
int f(int n, int mod, int k) {//k 底数 n当前分解 mod 完整的模数(p^k)
if (n == 0) {
return 1;
}
int ans = 1;
for (int i = 1; i <= mod; i++) {
if (i % k != 0) {
ans = ans * i % mod;
}
}
ans = qpow(ans, n / mod, mod);
for (int i = mod * (n / mod); i <= n; i++) {
if (i % k != 0) {
ans = ans * (i % mod) % mod;
}
}
ans = (ans * f(n / k, mod, k)) % mod;
return ans;
}
int g(int n, int k) {
if (n < k) {
return 0;
}
return g(n / k, k) + n / k;
}
int solve(int k, int mod) {
return f(n, mod, k) * inv(f(m, mod, k), mod) % mod * inv(f(n-m, mod, k), mod) % mod * qpow(k, g(n, k) - g(m, k) - g(n - m, k), mod) % mod;
}
int exlucas(int n, int m, int mod) {
int upp = sqrt(mod);
for (int i = 2; i <= upp; i++) {
if (mod % i == 0) {
++top;
pri[top] = i;
while (mod > 0 && mod % i == 0) {
pnum[top] ++;
mod /= i;
}
}
}
if (mod != 1) {
pri[++ top] = mod;
pnum[top] ++;
}
for (int i = 1; i <= top; i++) {
ans[i] = solve(pri[i], qpow(pri[i], pnum[i]));
}
int res = ans[1], resmod = qpow(pri[1], pnum[1]);
for (int i = 2; i <= top; i++) {
excrt(res, resmod, ans[i], qpow(pri[i], pnum[i]));
}
return res;
}
9.中国剩余定理(CRT)
9.1. 模数互质的情况
中国剩余定理可以求解如下的方程组的解,:
其中
我们可以构造地解出该方程组。
-
设
。 -
设
-
求解
在 意义下的逆元 。 -
设
。
答案即为
证明如下:
设
又有:
代入原式,得:
故对于
证毕!
CRT 代码:
int CRT()
{
int sump=0,ans=0;
for(int i=1;i<=n;i++)
sump*=p[i];
for(int i=1;i<=n;i++)
{
int b,y;
m[i]=sump/p[i];
exgcd(m[i],p[i],b,y);
invm[i]=b;
c[i]=invm[i]*m[i];
ans+=a[i]*c[i]%sump;
}
return (ans%sump+sump)%sump;
}
CRT 告诉了我们这样一件事,对于两两互质的数列
也就是说当我们想要求一个在
把 P 分解为质因数整数幂的成绩,对于每个质因数整数幂作为模数分别求解(这往往比任意合数好求),再用 CRT 合并,是数论题常见的解法——@zhqwq
这类问题的模板题:[SDOI2010] 古代猪文。
9.2. exCRT
求解:
的正整数解,其中
考虑前两个方程,将其合并,我们有:
由裴蜀定理,当
讨论有解情况时,可以将两边同时除以
得到
对于式
代入式
至此,我们完成了两个同余方程的合并,并得到了特解。容易发现,新的同余式子是在
因通解是容易求的,为:
我们不妨去想一想这是为什么,对
取模的结果,就是将整个整数集分成了 个等价类,如果等价类里有 个解,那肯定所有都是解。 一人得道,鸡犬升天。
——阮行止
按该方法进行合并即可。
void excrt(int &a, int &mod, int aa, int mmod) {
int g = __gcd(mod, mmod);
int m = mod / g * mmod;
int p, q;
exgcd(mod / g, mmod / g, p, q);
p = p * mod % m * (aa - a) / g % m;
a = (a + p + m) % m;
mod = m;
}
wrote on 23.11.3
upd on 23.11.28
upd on 23.12.1
upd on 24.1
upd on 24.9.1 这回应该真写完了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!