初等数论——同余
前置
模运算
定义:
加法:
减法:
乘法:
除法无法直接运算,要用逆元(在下面会讲到)。
平方运算: 快速幂
模运算满足: 结合律,交换律,分配律 。
同余的定义及其性质
定义: 若
由对于模n同余的所有整数组成的这个集合称为同余类(congruence class或residue class) 。
模
性质1: 满足对称性,同加性,同乘性,同幂性。
性质2: 若
费马小定理
若
引理:当
是质数时,其因子只有 和 两个。因此,若两个数相乘是 的倍数,其中必然至少有一个是 的倍数。
当不是 的倍数时,不存在 且 使得 而 是 的倍数,与 的限制矛盾。
进一步地,考虑所有数,它们乘以 之后在模 意义下互不相同,说明仍得 所有数。
因此,。又因为 显然不是 的倍数,所以费马小定理成立。
欧拉定理
若正整数
设
是模 的简化剩余系,那 也是模 的简化剩余系。
因为, ,所以欧拉定理成立。
可以发现费马小定理是欧拉定理的一种特殊情况。
扩展欧拉定理
若
若
因为证明有些复杂,见oi wiki 。
扩展欧拉定理可以用来降幂,当幂太大时,此公式可以减小复杂度,而当
线性同余方程
裴蜀定理
定义:
逆定理: 设
特殊地,设
当有
例题: P4549 【模板】裴蜀定理
code
#include<bits/stdc++.h>
using namespace std;
int n;
int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}
int main()
{
scanf("%d",&n);
int x,y;
cin>>x;
for(int i=2;i<=n;i++)
{
cin>>y;
x=gcd(x,y);
}
cout<<abs(x)<<endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int n;
int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}
int main()
{
scanf("%d",&n);
int x,y;
cin>>x;
for(int i=2;i<=n;i++)
{
cin>>y;
x=gcd(x,y);
}
cout<<abs(x)<<endl;
return 0;
}
扩展欧几里得算法
上文裴蜀定理,讲述了存在整数
我们设
也就是
又因
代入拆开得
可得一个解
现在发现只需要求出
考虑一下递推边界,当递推到
可转换成
显然
这样就求出了方程的一组特解,我们设为
显然不定方程
可以知道
所以通解为:
这里
实际做题时,会让求
特解的数据范围:ycx 的文章 关于 exgcd 求得特解的数值范围
线性同余方程
定义:形如
求法:
原式转换成 :
这个式子和扩展欧几里得算法可以求的式子有点关联。
设
原式又可转成:
这样用扩展欧几里得算法就可以求出。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int c,d,x,y;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=1;
return a;
}
int t=exgcd(b,a%b,y,x);
y-=(a/b)*x;
return t;
}
signed main()
{
scanf("%lld%lld",&c,&d);
exgcd(c,d,x,y);
printf("%lld",(x%d+d)%d);
return 0;
}
线性同余方程组(中国剩余定理)
中国剩余定理(crt)
上式子中
求解方法:
设
证明:
尝试为每个同余方程设一个
那
那要满足上面新设的同余方程,让
扩展中国剩余定理(excrt)
上面没看懂?没关系有更好理解并适用更广的扩展中国剩余定理(excrt),它不要求模数互质。
本质就是合并方程,我们把两个方程合并成新一个方程,依次类推就可以求解方程组了。
具体的说:合并方程
设
即:
这个地方可以用 exgcd 求解:
设:
由上文扩欧的知识知道:
然后把
根据 裴蜀定理 ,
现在求出了一个特解
模数为
引用:阮行止的洛谷题解。
从线性代数的角度讲,这个通解的构造方式是十分平凡的。对
证明解的唯一性,常常采用这样一种手段:假设
不妨设
模板:
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[20],b[20],ans,M=1,t[20],m[20];
int x,y;
int exgcd(int a,int b)
{
if(b==0)
{
x=1,y=0;
return a;
}
exgcd(b,a%b);
int t=y;
y=x-a/b*y;
x=t;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>b[i]>>a[i],M*=b[i];
for(int i=1;i<=n;i++)
{
exgcd(M/b[i],b[i]);
t[i]=x;
t[i]=(t[i]%b[i]+b[i])%b[i];
}
for(int i=1;i<=n;i++)
{
ans+=a[i]*(M/b[i])*t[i]%M;
}
cout<<ans%M<<endl;
return 0;
}
code
#include<bits/stdc++.h>
using namespace std;
typedef __int128_t ll;
const int N=1e7+10;
ll gcd(ll aa,ll bb)
{
if(!bb) return aa;
return gcd(bb,aa%bb);
}
ll lcm(ll aa,ll bb)
{
return aa/gcd(aa,bb)*bb;
}
ll exgcd(ll aa,ll bb,ll &x,ll &y)
{
if(!bb)
{
x=1,y=1;
return aa;
}
ll d=exgcd(bb,aa%bb,x,y);
ll z=x;
x=y;
y=z-(aa/bb)*y;
return d;
}
long long a[N],b[N],n;
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&a[i],&b[i]);
ll a1=a[1],b1=b[1];
for(int i=2;i<=n;i++)
{
ll a2=a[i],b2=b[i],x,y;
ll c=b2-b1;
ll d=exgcd(a1,a2,x,y);
if(c%d)
return puts("-1"),0;
c/=d;
x=x*c%(a2/d);
if(x<0) x+=(a2/d);
ll mod=lcm(a1,a2);
b1=(a1*x+b1)%mod;
if(b1<mod) b1+=mod;
a1=mod;
}
printf("%lld",(long long)((b1%a1)+a1)%a1);
return 0;
}
乘法逆元
定义
如果一个线性同余方程
应用: 上文提到除法无法直接取模,而用乘法逆元就可以求出。
根据定义可知
求解方法
求单个数的乘法逆元
当模数
由定义知,
再由费马小定理得:
所以
用快速幂就可以求出
code
int qpow(long long a, int b) {
int ans = 1;
a = (a % p + p) % p;
for (; b; b >>= 1) {
if (b & 1) ans = (a * ans) % p;
a = (a * a) % p;
}
return ans;
}
int main()
{
a=qpow(a,p-2);//求 a 的逆元
}
当
求乘法逆元本质上就是求线性同余方程,用求线性同余方程的方法求就可以了。
线性求
用递推来求逆元,当递推到
由此又可以推出
两边同乘
所以
既然是递推式,就要关注递推的起始项,当
code
inv[1] = 1;
for (int i = 2; i <= n; ++i) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
代码中
求任意
要求
定义两个数组
递推边界
code
s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2);
for (int i = n; i >= 1; --i) sv[i - 1] = sv[i] * a[i] % p;
for (int i = 1; i <= n; ++i) inv[i] = sv[i] * s[i - 1] % p;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验