裴蜀定理与扩展欧几里得

裴蜀定理与扩展欧几里得

裴蜀定理

定理

对于任意整数a,b,设d=gcd(a,b),则方程ax+by=d(sZ)必定有无数组整数解(x,y)

且对于d的任意整数倍也有解。

证明

d=gcd(a,b),sax+by的最小正值,我们需要证明d=s

r=bmods,则r=bqs(q=bsZ)

展开得r=bq(ax+by)=a(qx)+b(1qy),这时令r=ax+by,x=qx,y=1qy,则r也是ax+by的一个值。

根据模运算的性质有:0r<s,由于sax+by的最小正值,则r=0,故s|b

类似的,

r=amods,则r=aqs(q=asZ)

展开得r=aq(ax+by)=a(1qx)+b(qy),这时令r=ax+by,x=1qx,y=qy,则r也是ax+by的一个值。

根据模运算的性质有:0r<s,由于sax+by的最小正值,则r=0,故s|a

s|a,s|bs|gcd(a,b),s|d

再者,设a=md,b=nd。则ax+by=d(mx+ny),gcd(m,n)=1

由于s=ax+by=d(mx+ny)1,所以可以得到d|s

故有s|d,d|sd=s

QED.

裴蜀定理可以扩展到n元形式

也即n元一次方程i=1naixi=k的充要条件是gcd(a1,a2an)|k

证明过程模仿2元形式的证明,应用数学归纳法即可。

应用

裴蜀定理

如上文所说,只需要打一个GCD即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
signed main(){
    int n;cin>>n; 
    int a,ans;cin>>a;ans=a;
    for(int i=2;i<=n;i++){
        cin>>a;ans=gcd(ans,a);
        if(ans<0)ans=-ans;
    }
    cout<<ans<<"\n";
}

向量

首先,减去(a,b)等价于加上(a,b),其余同理,所以只需要考虑四个向量:(a,b),(b,a),(a,b),(b,a)

所以可以得到方程组:

{(AC)a+(BD)b=x(B+D)a+(A+C)b=y

gcd(a,b)=d,则两个方程有解的充要条件是d|x,d|y(裴蜀定理)。此时即得到了AC,A+C,BD,B+D的值,不妨记为f1,f2,f3,f4。则:A=f1+f22,C=f2f12,B=f3+f42,D=f4f32

所以说,f1,f2需要奇偶性相同,f3,f4同理,现在考虑如何保证奇偶性相同呢?不妨分类讨论:

  1. f1,f2,f3,f4均为偶数,此时有:x=f1a+f3b,提取一个公约数2,变成:d|x22d|x,同理可以得到:2d|y

2d|x,2d|y

  1. f1,f2,f3,f4均为奇数,此时有:x+a+b=(f1+1)a+(f3+1)b,再提取一个公约数2,变成2d|(x+a+b),2d|(y+a+b)

2d|(x+a+b),2d|(y+a+b)

  1. f1,f2为偶数,f3,f4为奇数,此时有:x+b=f1a+(f3+1)b,y+a=(f4+1)a+f2b,类似的我们可以得到:2d|(x+b),2d|(y+a)

2d|(x+b),2d|(y+a)

  1. f1,f2为奇数,f3,f4为偶数,此时有:x+a=(f1+1)a+f3b,y+b=f4a+(f2+1)b,类似的我们可以得到:2d|(x+a),2d|(y+b)

2d|(x+a),2d|(y+b)

四种情况满足一种即可,前提是d|xd|y

#include<iostream>
#include<cstdio>
using namespace std;
#define int long long//警钟长鸣
int gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}
signed main(){
	int n,a,b,x,y;cin>>n;
	while(n--){
		cin>>a>>b>>x>>y;
		int d=gcd(a,b);
		d<<=1;
		if(x%d==0&&y%d==0)cout<<"Y\n";
		else if((x+a)%d==0&&(y+b)%d==0)cout<<"Y\n";
		else if((x+b)%d==0&&(y+a)%d==0)cout<<"Y\n";
		else if((x+a+b)%d==0&&(y+a+b)%d==0)cout<<"Y\n";
		else cout<<"N\n";
	}
	return 0;
}

这个题是真的得手玩了。

引理:一个数mk进制下的数位和可以表示为m+x(1k)

证明:

由于一个数m总可以表示为i=0xiki(0xi<k)

分类讨论:

  1. m<k,此时x=0
  2. m=ki,(iZ)

此时k进制下数位和=1=x+(1x)=x+(1ki),而由于当k=1时,(1ki)=0,所以根据因式分解的知识,其必然存在一个因式为(1k)。此时说明(1ki)也符合引理。

  1. mZ

尝试对2.中的方法进行扩展。设其有n位,则m=i=0nxiki,数位和为i=0nxi

接着,将其拆开i=0nxi=i=0nxi·1=i=0nxi(ki+(1ki))=i=0nxiki+i=0nxi(1ki)=m+t(1k)

得证。

QED.

这样的话,我们就只需要判断方程:an+t(1k)c(modb)是否有解。

也即ax+(1k)y+bz=c有无解,裴蜀定理即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long//注意
int gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}
signed main(){
	int a,b,c,k,T;cin>>T;
    while(T--){cin>>a>>b>>c>>k;
		puts(c%(gcd(gcd(a,b),1-k))?"No":"Yes");	
	}
}

扩展欧几里得算法(exgcd)

应用:求解ax+by=gcd(a,b)的不定方程,并给出一组解。

算法思想

根据普通gcd,我们知道gcd(a,b)=gcd(b,amodb)

这就意味着ax+by=gcd(a,b)bx+(amodb)y=gcd(a,b)有解。

这启发我们运用这个过程去解决方程。

显然我们可以根据gcd一直化简到方程ax+0y=a。这是欧几里得算法的终末态。

此时显然有一组解:x=1,y=0。(y=0是为了防爆精度)。

然后考虑回推。

ax+by=gcd(a,b)是由ax+by=gcd(a,b)推出,这个不定方程的解为x0,y0。则显然a=b,b=amodb=aabb

带回式子,可以得到bx0+(aabb)y0=gcd(a,b)

整理一下,可以得到ay0+b(x0aby0)=gcd(a,b)

所以ax+by=gcd(a,b)的解x1=y0,y1=x0aby0

显然,求解gcd(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;
} 

那么回归正题:求解ax+by=t的方程。

根据裴蜀定理,其有解的充分必要条件是gcd(a,b)|t。现在我们来考虑求解它。

方法一

先解出ax0+by0=gcd(a,b),然后两式同乘tgcd(a,b),即可得到ax+by=t的解x1=x0tgcd(a,b),y1=y0tgcd(a,b)

方法二

显然,对于不定方程ax+by=1,有解的充要条件是gcd(a,b)=1,对于ax+by=t的方程,设d=gcd(a,b),将方程化为adx+bdy=td,解出方程adx+bdy=1,对答案乘td即可。

下面提供方法一的代码:

void solve(int a,int b,int t,int &x,int &y){  //ax+by=t
    int d=exgcd(a,b,x,y);
    if(t%d){  
        puts("无解");return ;
    }
    x*=t/d,y*=t/d;
}

无穷解的讨论

ax+by=t有解,且一组解为x0,y0,下面证明它有无穷多组解。

a(x0+Δx)+b(y0Δy)=t(Δx,ΔyZ)

拆开得到aΔx=bΔyab=ΔyΔx=mgcd(a,b)ngcd(a,b)=mn

显然,满足这个关系式的最小整数对(Δx,Δy)(n,m)。且其任意整数倍都是符合条件的。

故可以得到:

{Δx=kbgcd(a,b)Δy=kagcd(a,b)

其中k为任意整数。

那么借助它,我们可以得到一些方法:

对于不定方程ax+by=t中,满足x为最小正值,则将任意解中的x0bgcd(a,b)取模(记

得防负数)即可。同时就可以求出对应的y值。

同余方程

还有一个应用就是解同余方程axt(modb)

这个式子可以转化为求解不定方程axby=t

解出这个式子,所有的x都是合法的,无解说明不存在。

应用

青蛙的约会

根据题意,设p为跳的次数,则容易得到同余方程:

x+mpy+np(modL),移项变式得到:

p(mn)yx(modL),等价于求解p(mn)+qL=yx

q的最小正值解即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
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(){
	int a,b,m,n,l,x,y;cin>>x>>y>>m>>n>>l;
	if(n<m){
		swap(n,m);swap(x,y);
	}
	int d=exgcd(n-m,l,a,b);
	int p=x-y;
	if(p%d){
		cout<<"Impossible\n";
	}
	else {
		a=(a*(p/d)%(l/d)+(l/d))%(l/d);cout<<a<<"\n";
	}
}

最大公约数问题

描述:求不定方程ax+by=gcd(a,b)的一组解(x0,y0),满足|x0|+|y0|最小。

显然,由于min(a,b)gcd(a,b)1,故这组解中x,y其中一个就是最小正值,把两种情况算出取min即可。

#include<iostream>
#include<cstdio>
using namespace std;
inline int abs(int x){
    return (x<0?-x:x);
}
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;
}
int main(){
	int t,a,b,x,y;
	cin>>t;
	while(t--){
		cin>>a>>b;
		int d=exgcd(a,b,x,y);
		int ans=1e9;
		x=(x%b+b)%b,y=(d-a*x)/b;
		ans=x-y;int xx=x,yy=y;
		y=(y%a+a)%a,x=(d-b*y)/a;
		if(abs(xx)+abs(yy)<abs(x)+abs(y))cout<<xx<<" "<<yy<<"\n";
		else cout<<x<<" "<<y<<"\n";
	}
} 

荒岛野人

这个题注意观察到n15,M106。这启发我们直接暴力自大到小枚举答案,然后枚举每一对野人,以青蛙的约会的方法判断有无解。

注意枚举的下界就是野人居住洞穴的编号最大值。

注意不能二分/倍增,会爆掉的。(答案是不规则的)。

#include<iostream>
#include<cstdio>
using namespace std;
#define N 105
#define int long long
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;
} 
int c[N],p[N],l[N],n;
bool check(int mid){
	int x,y;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			int a=p[i]-p[j],b=mid,C=c[j]-c[i];
			int d=exgcd(a,b,x,y);
			if(C%d)continue;
			x=x*(C/d);b/=d;
			if(b<0)b=-b;
			int ti=(x%b+b)%b;
			if(ti>l[i]||ti>l[j])continue;
			return false;
		}
	}
	return true;
}
signed main(){
	cin>>n;
	int ans=0;
	for(int i=1;i<=n;i++){
		cin>>c[i]>>p[i]>>l[i];
		ans=max(ans,c[i]);
	}
	while(!check(ans))++ans;
	cout<<ans<<"\n";
}

conscious

观察数据范围1a,sub<m,可以发现本题就是让我们求出ax+ym=s的最小正值解x,且|ssub|是最小的。

借助裴蜀定理,我们其实是可以确定|ssub|的值的。

根据裴蜀定理,ax+ym可以得到所有gcd(a,m)的整数倍。这个s也就转化了。

所以我们可以发现,可能的s只有两个:subgcd(a,m)gcd(a,m),subgcd(a,m)gcd(a,m)

注意subgcd(a,m)gcd(a,m)有可能大于等于m,此时这个解就需要被舍弃。

这两个决策谁更优呢?设k=submodgcd(a,m),则:

  1. 2k<gcd(a,m),说明subgcd(a,m)gcd(a,m)更优
  2. 2k=gcd(a,m),说明subgcd(a,m)gcd(a,m),subgcd(a,m)gcd(a,m)一样优,此时对二者的x的最小正值取较小值。
  3. 2k>gcd(a,m),说明subgcd(a,m)gcd(a,m)更优。

求出最优方案中对应的x,然后直接计算答案:axmodm

注意若|subaxmodm|sub,直接将答案设为0即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
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;
}
int solve(int a,int b,int k){//ax+by=k 
	int x,y;int d=exgcd(a,b,x,y);
	b/=d,x*=k/d;
	return (x%b+b)%b;  
}
signed main(){
	ios::sync_with_stdio(false);
	int T;cin>>T;
	while(T--){
		int sb,ans,res,a,m,sub,x,y;cin>>a>>m>>sub;int d=exgcd(a,m,x,y);
		int p=sub%d;
		if(sub+d-p>=m||p+p<d)
			ans=solve(a,m,sub-p);
		else if(p+p>d)
			ans=solve(a,m,sub+d-p);
		else {
			ans=min(solve(a,m,sub-p),solve(a,m,sub+d-p));
		}
		res=ans;ans=((res*a)%m+m)%m;
		if(abs(ans-sub)>=sub){
			cout<<"0 0\n";continue;
		}
		cout<<ans<<" "<<res<<"\n";
	}
	return 0;
}

AHOI2005洗牌

注意观察,手玩几组会发现一个规律:位置为x的数,交换一次就会变成2xmod(n+1)

所以本题实际解决的是:L2mx(modn+1)

进行移项变式,可以得到x2m+y(n+1)=L。求出最小正值解即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define int __int128
#define ll long long
void read(int &x){
    x=0;
    char ch=getchar();
    while(ch>'9'||ch<'0')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=getchar();
}
int power(int a,int b,int p){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans%p;
}
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(){
    int n,m,l;
	read(n),read(m),read(l);
	int x,y;int d=exgcd(power(2,m,n+1),n+1,x,y);
	x=(x%(n+1)+(n+1))%(n+1); 
	int inv=x;ll ans=inv%(n+1)*l%(n+1);
	cout<<ans; 
}
posted @   spdarkle  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示