浅记线性同余方程(组)

线性同余方程

定义

线性同余方程就是形如 \(ax\equiv b\pmod m\) 其中 \(a,b,m\) 是给定的整数。

解法

由同余的性质可知 \(m\mid ax-b\) 即 \(ax-b=km\) 其中 \(k\in \mathbb{Z}\)。

如果我们设 \(k=-y\) 的话,就有 \(ax+my=b\),发现了吗?其实这就是 Bézout 定理

Bézout 定理 我们可以得到,这个同余方程有解当且仅当 \(\gcd(a,m)\mid b\)。

我们考虑在有解的情况下使用扩展欧几里得算法先求解出 \(ax+my=\gcd(a,m)\) 的一组特解 \(\left\{\begin{matrix}x=x_0 \\y=y_0\end{matrix}\right.\),然后呢,我们就可以得到 \(x=\frac{x_0\times b}{\gcd(a,m)}\) 就是原方程的一组解。

关于扩展欧几里得算法的说明

内容

我们考虑不定方程 \(ax+by=\gcd(a,b)\) 的一组特解,我们可以采用递归的方法来求解,实际上这也就是扩展欧几里得算法:

  1. 显然当 \(b=0\) 时,有 \(\left\{\begin{matrix}x=1\\y=0\end{matrix}\right.\) 满足条件。
  2. 当 \(b\ne 0\) 时,我们根据欧几里得算法有 \(\gcd(a,b)=\gcd(b,a\bmod b)\) 于是,我们就有 \((a\bmod b)y+bx=\gcd(b,a\bmod b)\) 又由于 \((a\bmod b)y+bx=\left(a-b\times\lfloor\frac{a}{b}\rfloor\right)\times y+bx\) 将 \(\operatorname{RHS}\) 展开,合并同类项后有 \((a\bmod b)y+bx=ay+b\times \left(x-\lfloor\frac{a}{b}\rfloor y\right)\) 于是,我们令 \(x_0=y,y_0=x-\lfloor \frac{a}{b}\rfloor y\) 就有 \(ax_0+by_0=\gcd(a,b)\)。
代码实现

根据上述内容,我们可以打出扩展欧几里得算法的代码:

cpp 复制代码
int exgcd(int a,int b,int&x,int&y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	int d=exgcd(b,a%b,x,y);
	int z=x;
	x=y;
	y=z-z*y;
	return d;
}
功能介绍

以上函数的返回值为 \(\gcd(a,b)\),注意到参数 \(x,y\) 均在前面加上了取地址符,表示在函数中可以改变 xy 的值,而函数运行完成后 xy 所保存的值就是 \(ax+by=\gcd(a,b)\) 的一组特解。

一道模板题

洛谷 P1082

这道题目就是模板题,方程可以写成 \(ax+by=1\) 的形式,于是我们使用扩展欧几里得算法,可以求出特解 \(x_0\) 然后 \(x_0\bmod b\) 就是原方程的最小正整数解了。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b;
int exgcd(int a,int b,int&x,int&y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	int d=exgcd(b,a%b,x,y);
	int z=x;
	x=y;
	y=z-(a/b)*y;
	return d;
}
signed main(){
	cin>>a>>b;
	int x,y;
	exgcd(a,b,x,y);
	cout<<(x%b+b)%b;
	return 0;
}

线性同余方程组

作者太懒了,这里先讲解更加宽泛的扩展中国剩余定理吧,等以后再讲解特殊的中国剩余定理,顺便宣传一下博客:link

问题简述

给定一个 \(k\) 个方程的线性同余方程组:

\\\left\\{\\begin{matrix} x\\equiv a_1\\pmod {m_1}\\\\x\\equiv a_2\\pmod {m_2} \\\\\\vdots \\\\x\\equiv a_k\\pmod {m_k} \\end{matrix}\\right.\\

其中 \(m_1,m_2,\dots,m_k\) 不一定两两互质。

解题方法

我们的大致解题思路为将 \(2\) 个方程合并为一个新的方程,以此类推,最终我们会得到一个 \(x\equiv y\pmod z\) 的一个方程,易见上面的方程组的最小正整数解就是 \(y\)。

接下来我们来解决合并方程的问题,我们考虑如下两个方程:

\\\left\\{\\begin{matrix} x\\equiv a_1\\pmod {m_1}\\\\x\\equiv a_2\\pmod {m_2} \\end{matrix}\\right.\\

我们根据第一个式子可以写出 \(x\) 的通解 \(x=a_1+m_1\times k\) 其中 \(k\) 为任意整数,我们将这个通解带入第二个式子就可以得到 \(a_1+m_1\times k\equiv a_2\pmod {m_2}\) 我们移一下项就可以得到 \(m_1\times k\equiv a_2-a_1\pmod {m_2}\),这就是上面的方程组合并后的结果。

而这个方程有解的充要条件是 \(\gcd(m_1,m_2)\mid a_2-a_1\),这个其实就是裴蜀定理,这里不再概述。

我们继续讲,我们得到这个充要条件后我们可以判断这个方程是否有解,如果有解我们就继续进行接下来的操作。

我们设 \(d=\gcd(m_1,m_2)\),然后将我们合并的方程变换一下就是:

\\\frac{m_1\\times k}{d}\\equiv \\frac{a_2-a_1}{d}\\pmod {\\frac{m_2}{d}} \\

然后,我们设 \(m_1'=\frac{m_1}{d},c=\frac{a_2-a_1}{d},m_2'=\frac{m_2}{d}\) 于是我们就有:

\m_1'\\times k\\equiv c\\pmod {m_2'} \\

注意到此时 \(m_1',m_2'\) 互质,所以 \(m_1'\) 在模 \(m_2'\) 的意义下存在乘法逆元,我们可以使用扩展欧几里得算法来求出逆元,即求出整数 \(inv\) 使得 \(m_1'\times inv\equiv 1\pmod {m_2'}\),所以我们继续将这个方程变换就变成了:

\k\\equiv c\\times inv\\pmod {m_2'} \\

如果我们记 \(k_0=c\times inv\) 则 \(k\) 的通解为 \(k_0+m_2'\times t\) 其中 \(t\) 为任意整数。

然后我们将这个 \(k\) 带回一开始的式子就可以得出:

\\\begin{aligned} x\&=a_1+m_1\\times(k_0+m_2'\\times t)\\\\ \&=(a_1+m_1\\times k_0)+(m_1\\times m_2')\\times t\\\\ \&=(a_1+m_1\\times k_0)+\\frac{m_1\\times m_2}{d}\\times t\\\\ \&=(a_1+m_1\\times k_0)+\\mathrm{lcm}(m_1,m_2)\\times t\\end{aligned}\\

我们设 \(x_0=a_1+m_1\times k_0,L=\mathrm{lcm}(m_1,m_2)\) 所以我们就愉快地得出了:

\\\left\\{\\begin{matrix} x\\equiv a_1\\pmod {m_1}\\\\x\\equiv a_2\\pmod {m_2} \\end{matrix}\\right.\\Longleftrightarrow x\\equiv x_0\\pmod L\\

于是,我们完成了合并方程的使命!

最后其实就是一个递推的过程我们一次合并前 \(2\) 个方程,最后就能得到答案!

代码实现

cpp 复制代码
#include<bits/stdc++.h>
#define LL __int128
#define R register
using namespace std;
namespace fastIO{char *p1,*p2,buf[100000];
	#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
	inline void read(LL&n){LL x=0,f=1;char ch=nc();while(ch<48||ch>57){if(ch=='-'){f=-1;}ch=nc();}while(ch>=48&&ch<=57){x=(x<<3)+(x<<1)+(ch^48),ch=nc();}n=x*f;}inline void read(string&s){s="";char ch=nc();while(ch==' '||ch=='\n'){ch=nc();}while(ch!=' '&&ch!='\n'){s+=ch,ch=nc();}}inline void read(char&ch){ch=nc();while(ch==' '||ch=='\n'){ch=nc();}}inline void write(LL x){if(x<0){putchar('-'),x=-x;}if(x>9){write(x/10);}putchar(x%10+'0');return;}inline void write(const string&s){for(R LL i=0;i<(int)s.size();i++){putchar(s[i]);}}inline void write(const char&c){putchar(c);}
}using namespace fastIO;
inline LL mul(LL a,LL b,const LL&mod){
    a=(a%mod+mod)%mod; 
    b=(b%mod+mod)%mod;
    LL res=0;
    while(b){
        if(b&1)res=(res+a)%mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return res;
}
void exgcd(LL a,LL b,LL&x,LL&y){
    if(b==0){
        x=1;
        y=0;
    }
	else{
        exgcd(b,a%b,y,x);
        y-=x*(a/b);
    }
}
LL inv_mod(LL a,LL m){
    LL x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
LL gcd(LL a,LL b){
    return b?gcd(b,a%b):a;
}
LL n,a[100005],b[100005];
signed main(){
    read(n);
    for(int i=0;i<n;i++){
    	read(a[i]);
    	read(b[i]);
    }
    LL a0=a[0];
    LL b0=(b[0]%a0+a0)%a0;
    for(int i=1;i<n;i++){
        LL ai=a[i];
        LL bi=(b[i]%ai+ai)%ai;
        LL d=gcd(a0,ai);
        LL dif=bi-b0;
        LL a0_=a0/d;
        LL ai_=ai/d;
        LL dif_=dif/d;
        LL c=(dif_%ai_+ai_)%ai_;
        LL inv=inv_mod(a0_,ai_);
        LL t0=mul(inv,c,ai_);
        LL a0__=(a0/d)*ai;
        LL mod__=a0__;
        LL p=mul(a0,t0,mod__);
        LL b0__=(b0+p)%mod__;
        a0=mod__;
        b0=b0__;
    }
	write(b0);
    return 0;
}

一些例题

如果有不会的可以回复作者!

相关推荐
Luhui Dev1 天前
几何图,现在可以用 API 一句话生成
人工智能·数学·luhuidev
xcLeigh1 天前
数学之美:数字革命背后的底层逻辑
人工智能·数学·ai·数学原理·书籍·数学之美·绝对边界
Malone-AI1 天前
f(n)=af(n-1)+b根据一阶线性递推式推导通项公式
数学·数列
闻缺陷则喜何志丹1 天前
【解析几何丘维声 第二章】空间的平面和直线
数学·向量·解析几何·点乘·叉乘
Sunsets_Red3 天前
ABC462D 题解
c++·数学·编程·比赛·atcoder·信息学竞赛·信息学
databook4 天前
用SymPy自动因式分解:从面积拼图到代数恒等式
python·数学·动效
铸人4 天前
关于零的一些讨论
数学·极限·复数
装不满的克莱因瓶5 天前
自动微分的原理:计算图与前向传播
人工智能·pytorch·python·数学·ai·微积分·计算图
闻缺陷则喜何志丹5 天前
【解析几何丘维声 第一章】向量代数第二部分
数学·向量·计算几何·点乘·叉乘
装不满的克莱因瓶6 天前
掌握多头自注意力机制(Multi-Head Self-Attention)——Transformer 强大表达能力的核心来源
人工智能·python·深度学习·数学·ai·transformer