同余基础数论详解
本文主要讲了扩展欧几里得、(扩展)中国剩余定理。
扩展欧几里得
小结论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;
}
有特殊要求的解不定方程
这题比普通的不定方程多了一些要求,其中输出-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)}\)即可
有解说明\(\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需要改下读入)
现在比如要求\(\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;
}