一元线性同余方程(扩展欧几里得算法,C Looooops)


前言

贝祖定理:
若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。

但如何求出满足条件的x、y就是我们接下来的任务。

需要注意的是这个方程是丢番图方程。
丢番图方程(Diophantine Equation):有一个或者几个变量的整系数方程,它们的求解仅仅在整数范围内进行。最后这个限制使得丢番图方程求解与实数范围方程求解有根本的不同。丢番图方程又名不定方程、整系数多项式方程,是变量仅容许是整数的多项式等式。

作为不定方程,变量个数多于方程个数,将会存在多个解,我们只需要找到其中的特解,通过特解来表示通解。

若我们找到了一组特解x,y可使得方程成立,
那么通解可以表示为:
X = x + b d t X = x + \frac{b}{d} t X=x+dbt
Y = y − a d t Y = y - \frac{a}{d} t Y=ydat
b d \frac{b}{d} db a d \frac{a}{d} da为互质关系,从而通过整数 t 的不同取值可以得到所有可能的值。
两式后面的余项在方程中可以进行抵消,从而不影响等式的成立。


一、推导

1.反递推起点

在欧几里得算法中,我们通过辗转相除可以得到:
当前的a = gcd,b = 0;
此时由于b 等于0,我们可以解出在方程 a x + b y = d ax+by=d ax+by=d中,
x = 1;
y的值随意,不妨令其为 0;

2.反递推

设当前层的a、b为A、B,根据辗转相除可知下一层的a、b为B、A%B。

若我们已经求出来在下一层中满足ax+by=d的x、y,并以X、Y来表示。
那么此时满足方程: B ∗ X + ( A % B ) ∗ Y = d B * X + (A\%B)* Y = d BX+A%BY=d
A = K ∗ B + R A = K * B + R A=KB+R,有 R = A % B = A − A B ∗ B R = A \% B =A -\frac{A}{B}* B R=A%B=ABAB
d = B ∗ X + ( A − A B ∗ B ) ∗ Y d = B * X + (A - \frac{A}{B} * B )* Y d=BX+ABABY
可化简得:

d = A ∗ Y + B ∗ ( X − A B ∗ Y ) d = A * Y + B * (X-\frac{A}{B}*Y) d=AY+BXBAY

当前层的 x = Y , y = X − A B ∗ Y x = Y,y = X-\frac{A}{B}*Y x=Yy=XBAY
那么我们就可以根据这个来反推出在最初的式子中的x、y;


二、代码实现

在原来欧几里得算法的基础上进行修改,在求最大公约数的同时求出满足条件的x、y。

int exgcd(int a, int b, int &x, int &y){//最终返回的是gcd(a,b)
	if(!b) {
		x=1;y=0;
		return a; //到达递归边界返回
	}
	int r=exgcd(b,a%b,x,y);
	int temp=y; //把x y变成上一层的
	y=x-a/b*y;
	x=temp;
	return r; //得到a b的最大公因数
}
void exgcd(int a, int b, int &d, int &x, int &y){
	if(!b) d=a, x=1, y=0;
	else{
		exgcd(b,a%b,d,y,x);
		y-=x*(a/b);
	}
}

三、例题·C Looooops

C Looooops

问题描述

给定A,B,C,K
2 K 2^K 2K为模,问能否使得循环
f o r (   i = A ; i   ! = B ; i + = C ) for( \ i=A;i\ !=B;i+=C) for( i=A;i !=B;i+=C)结束:
若无法循环出结果,则输出 F O R E V E R FOREVER FOREVER
若可以结束,则输出循环次数。

多组输入,当 A = = B = = C = = K = = 0 A==B==C==K==0 A==B==C==K==0时结束程序。


分析

若循环可以结束,设循环次数为 x x x,则存在式子:
A + x C ≡ B ( m o d   2 K ) → x C ≡ B − A ( m o d   2 K ) A+xC≡B(mod\ 2^K)\rightarrow xC≡B-A(mod\ 2^K) A+xCB(mod 2K)xCBA(mod 2K)
可以用拓展欧几里得求解:
x C + y 2 K = g c d ( C , 2 k ) xC+y2^K=gcd(C,2^k) xC+y2K=gcd(C,2k)

( B − A ) % g c d ( C , 2 K ) ! = 0 (B-A)\%gcd(C,2^K)!=0 (BA)%gcd(C,2K)!=0,说明方程无解,输出"FOREVER";
( B − A ) % g c d ( C , 2 K ) = = 0 (B-A)\%gcd(C,2^K)==0 (BA)%gcd(C,2K)==0,说明方程有解,再根据这个解求出满足原方程的最小正整数解。

注意:给定k后,要把k换算为 2 k 2^k 2k,在默认使用 l o n g l o n g long long longlong的前提下,如果是使用位运算,要注意写为 k = ( l l ) 1 < < k ; k=(ll)1<<k; k=(ll)1<<k;
如果使用函数 p o w ( ) pow() pow(),则不必注意。

代码

#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;

ll exgcd(ll a, ll b, ll &x, ll &y){//最终返回的是gcd(a,b)
	if(!b) {
		x=1;y=0;
		return a; //到达递归边界返回
	}
	ll r=exgcd(b,a%b,x,y);
	ll temp=y; //把x y变成上一层的
	y=x-a/b*y;
	x=temp;
	return r; //得到a b的最大公因数
}

int main(){
	ll a,b,c,k;
	ll x,y,d,e;
	while(1){
		cin>>a>>b>>c>>k;
		if(a==b&&b==c&&c==k&&k==0)break;
		k=pow(2,k);
		e=b-a;
		d=exgcd(c,k,x,y);
		if(e%d)
			cout<<"FOREVER"<<endl;
		else {
			ll s=k/d;
			ll ans=(x*e/d%s+s)%s;
			cout<<ans<<endl;
		}		
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值