同余基础数论详解

本文主要讲了扩展欧几里得、(扩展)中国剩余定理。

扩展欧几里得

小结论1:当\(x,y\)为整数时,\(ax+by\)最小正整数值为\(\gcd(a,b)\)

我们设\(ax+by\)最小值为\(s\),对应不定方程的解为\(x_0,y_0\)

可以发现\(\gcd(a,b)|ax_0,\gcd(a,b)|by_0\)

那么\(\gcd(a,b)|ax_0+by_0=s\)

再设\(a\div s=p\cdots\cdots q(0\leq q<s,带余除法 )\)

\(a=ps+q=p(ax_0+by_0)+q\)

可得\(q=a(1-px_0)+b(-py_0)\),是\(ax+by\)的形式

而且\(s\)\(ax+by\)最小正整数值,\(q\)为整数,\(0\leq q<s\)

可得\(q=0\)\(s|a\)

同理可得\(s|b\),那么\(s|\gcd(a,b)\)

而且上面也推出\(\gcd(a,b)|s\)最后得到\(s=\gcd(a,b)\)

小结论2:当\(a,b,c\)为整数时,\(ax+by=c\)有整数解的充分必要条件是\(\gcd(a,b)|c\)

充分性:由小结论1得,\(ax+by=\gcd(a,b)\)有整数解,只需将解乘上相应的倍数即是\(ax+by=c\)的解。

必要性:\(\gcd(a,b)|a,\gcd(a,b)|b\)\(\gcd(a,b)|ax+by=c\)

扩展欧几里得

我们可以发现,\(\gcd(a,b)=\gcd(b,a\%b)\),那我们可以试试找出\(ax+by=\gcd(a,b)\)\(bx^{\prime}+(a\%b)y^{\prime}=\gcd(a,b)\)的解的关系。

首先有\(ax+by=\gcd(a,b)=bx^{\prime}+(a\%b)y^{\prime}\)

\(a\%b=a-b\lfloor\dfrac{a}{b}\rfloor\)代入后整理一下得到

\(ax+by=ay^{\prime}+b(x^{\prime}-\lfloor\dfrac{a}{b}\rfloor y^{\prime})\)

可以得到其中一组解为\(\begin{cases}x=y^{\prime}\\y=x^{\prime}-\lfloor\dfrac{a}{b}\rfloor y^{\prime}\end{cases}\)

代码递归到\(b=0\)时,方程就变为\(ax+0y=\gcd(a,0)\),一组解就是\(x=1,y=0\),还可以顺便求\(\gcd(a,b)\)

void exgcd(ll a,ll b,ll&x,ll&y,ll&gcd) {
	if (!b) gcd=a,x=1,y=0;
	else exgcd(b,a%b,y,x,gcd),y-=a/b*x;
}

有特殊要求的解不定方程

P5656 【模板】二元一次不定方程 (exgcd)

这题比普通的不定方程多了一些要求,其中输出-1当且仅当\(\gcd(a,b)\)不是\(c\)的因数,所以我们这里只讨论有解的情况。

由于方程\(ax+by=c\)\(\dfrac{a}{\gcd(a,b)}x+\dfrac{b}{\gcd(a,b)}y=\dfrac{c}{\gcd(a,b)}\)解相同,下面就只考虑\(\gcd(a,b)=1\)的情况。

我们可以看一下不定方程解的性质:

比如我们已经用exgcd求出一组解\((x_0,y_0)\)

可以发现当\(t\)为任意整数时,\(a(x_0+tb)+b(y_0-ta)=c\)(把括号拆开就是\(ax_0+by_0=c\)

所以这个方程的通解为\(\begin{cases}x=x_0+tb\\y=y_0-ta\end{cases}\)

假设使\(y\)也为正整数的\(x\)的最小正整数值为\(x_1\),最大正整数值为\(x_2\)

根据通解的形式,\(x_1,x_1+b,x_1+2b,\cdots,x_2\)都对应一组正整数解。

也就是正整数解得个数为\(\dfrac{x_2-x_1}{b}+1\)

我们可以先求出\(x\)的最小正整数值:

由通解的形式得:\(x=x_0+tb\),则\(x\)最小正整数值\(x_1\)就是\(x_0\%b\),这样我们同事还可以求出最大值\(y_1=\dfrac{c-ax_1}{b}\)

同理\(y\)最小值\(y_2\)就是\(y_0\%b\)\(y_1\%b\)\(x_2=\dfrac{c-bx_2}{a}\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void exgcd(ll a,ll b,ll&x,ll&y,ll&gcd) {
	if(!b) gcd=a,x=1,y=0;
	else exgcd(b,a%b,y,x,gcd),y-=a/b*x;
}
ll a,b,c,d,x,y,x2,y2,t,z;
int main() {
	scanf("%d",&t);
	while(~scanf("%lld%lld%lld",&a,&b,&c)) {
		exgcd(a,b,x,y,d);
		if(c%d) {//无解
			printf("-1\n");
			continue;
		}
		a/=d,b/=d,c/=d,x*=c,y*=c;//先求出x0,y0,除以gcd后才是我们讨论的情况
		x=x%b>0?x%b:x%b+b,y=(c-a*x)/b;//算出x1,y1
		y2=y%a>0?y%a:y%a+a,x2=(c-b*y2)/a;//算出x2,y2
		if(y>0) {
			printf("%lld %lld %lld %lld %lld\n",(x2-x)/b+1,x,y2,x2,y);
		} else {
			printf("%lld %lld\n",x,y2);
		}
	}
	return 0;
}

同余方程

\(ax\equiv c\pmod b\)转换成\(ax+by=c\)就可以解了。

由小结论2得,若\(\gcd(a,b)|c\),则有整数解,否则无整数解。

有解的时候,先解\(ax+by=\gcd(a,b)\)的解,再将解乘上\(\dfrac{c}{\gcd(a,b)}\)即可

例题:P1082 [NOIP2012 提高组] 同余方程

有解说明\(\gcd(a,b)|1\),那么\(\gcd(a,b)=1\),直接解同余方程即可

注:这其实就是求\(a\)\(b\)意义下的逆元,逆元相关见逆元与(扩展)欧拉定理

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a,mod,x,y,d;
void exgcd(ll a,ll b,ll&x,ll&y,ll&gcd) {
	if (!b) gcd=a,x=1,y=0;
	else exgcd(b,a%b,y,x,gcd),y-=a/b*x;
}
int main() {
	scanf("%lld%lld",&a,&mod);
	exgcd(a,mod,x,y,d);
	printf("%lld\n",(x%mod+mod)%mod);//记得取模
	return 0;
}

例题:P1516 青蛙的约会

题目即求\(x+km\equiv y+kn\pmod l\)\(k\)

整理下式子就是\(k(m-n)\equiv y-x\pmod l\)

直接解同余方程就行了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll m,n,x,y,b,c,a,gcd;
ll exgcd(ll a,ll b,ll&x,ll&y,ll&gcd) {
	if(!b)x=1,y=0,gcd=a;
	else exgcd(b,a%b,y,x,gcd),y-=a/b*x;
}
int main() {
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&b),a=m-n,c=y-x;
	if(a<0)a=-a,c=-c;
	exgcd(abs(a),b,x,y,gcd);
	if(c%gcd)puts("Impossible");
	else printf("%lld\n",(c/gcd*x%(b/gcd)+b/gcd)%(b/gcd));
	return 0;
}

多组同余方程的通解

例题,三倍经验:(T3需要改下读入)

P1495 【模板】中国剩余定理(CRT)/曹冲养猪

P4777 【模板】扩展中国剩余定理(EXCRT)

P3868 猜数字

现在比如要求\(\begin{cases}x\equiv a_1\pmod {b_1}\\\cdots\\x\equiv a_n\pmod {b_n}\end{cases}\)的最小正整数解

我们假设前\(n-1\)个方程的最小正整数解为\(s\)\(\operatorname{lcm}(b_1,b_2,\cdots,b_{n-1})=m\)

那么我们可以得到前\(n-1\)个方程的所有解都可以表示成\(s+xm\)\(x\)为整数)

现在我们就是要找一个\(x\)使\(s+xm\)也满足第\(n\)个方程

那么\(s+xm\equiv a_n\pmod{b_n}\)\(xm\equiv a_n-s\pmod{b_n}\)

转换不定方程\(mx+b_ny=a_n-s\)

如果\(\dfrac{a_n-c}{\gcd(m,b_n)}\)不为整数,则整个方程都无解,否则用扩展欧几里得算出\(x\)后算出\(s+xm\)即可。

也就是说,\(s\leftarrow s+xm,m\leftarrow m\times\dfrac{b_n}{\gcd(m,b_n)}\)就能将第\(n\)个方程也加进去。

我们只要让\(s\)初始值为\(a_1\)\(m\)初始值为\(b_1\),之后把后面\(n-1\)个方程逐个加入即可。

注:\(x\)一定要对\(\dfrac{b_n}{\gcd(m,b_n)}\)取模,因为会乘上\(m\),还要对\(\operatorname{lcm}(m,b_n)=\dfrac{b_n}{\gcd(m,b_n)} m\)取模,对答案虽不会有影响,但下面的代码的***处可能导致精度溢出

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
void exgcd(ll a,ll b,ll&x,ll&y,ll&gcd) {
	if (!b) gcd=a,x=1,y=0;
	else exgcd(b,a%b,y,x,gcd),y-=a/b*x;
}
ll mul(ll ai,ll bi,ll p) {//快速计算a*b%p(保证精度)
	ll ansp=0,x=(ai+p)%p;
	bi=(bi+p)%p;
	while(bi!=0) {
		if(bi&1)ansp=(ansp+x)%p;
		x=(x+x)%p;
		bi>>=1;
	}
	return (ansp%p+p)%p;
}
ll mod,ans,x,y,b,c,d,a,m;
int main() {
	scanf("%d",&n);
	scanf("%lld%lld",&mod,&ans);//第一个方程的解即是a1
	for(int i=1; i<n; ++i) {
		scanf("%lld%lld",&m,&a);
		exgcd(mod,m,x,y,d);
		b=ans-a,c=m/d;//b取了相反数,后面就用了减
		if(b%d) {//判断无解
			ans=-1;
			break;
		}
		x=(mul(b/d,x,c)+c)%c;
		ans-=x*mod;// ***
		mod*=c;//计算最小公倍数
		ans=(ans%mod+mod)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-03-11 12:32  mod998244353  阅读(558)  评论(0编辑  收藏  举报
Live2D