【数论基础】扩欧的应用及乘法逆元
老师讲了3个应用
1.求解不定方程ax+by=c
方程ax+by=c并不总是有解,当且仅当gcd(a,b) | c有解。
由裴蜀定理知一定存在x0,y0使得a*x0+b*y0=gcd(a,b)。则可由exgcd求出x0,y0,
则x1=c/d*x0,y1=c/d*y0为方程ax+by=c的一组解,(d=gcd(a,b))
则方程ax+by=c的通解为
x=x1+b/d*k,y=y1-a/d*k
x=x1+b/d*k,y=y1-a/d*k
证明如下:
a*x1+b*y1=a*x+b*y=c
a(x1-x)=b(y-y1)
a/d(x1-x)=b/d(y-y1),此时a/d和b/d一定互素,因此x1-x一定是b/d的整数倍,
设为k*(b/d),同理y-y1一定是a/d的整数倍,设为k*(a/d),因此任意解为(x1+b/d*k,y1-a/d*k),k取任意整数。
2.求解模线性方程
对于线性同余方程:ax≡m(mod b) 转化为ax+by=m则可直接求解。
看两个例题
简单讲一下推导过程:
这题要解这个方程
方程等价于
再变一下
令S=x-y,W=n-m(注意这个地方的变号)
得到一个不定方程
这个题就是求出k的最小值,那么先求出一组特解,再推导出最小解。
由于exgcd的使用时,方程变成了
该方程的所有解就表示为
由于
则
而扩欧的解是建立在求gcd(W,l)上的,所以还要乘上一个(S/gcd(W,l))。
代码如下:
#include<cstdio> #include<iostream> #include<cmath> #define int long long using namespace std; int xx,yy,x,y,m,n,l; inline int ex_gcd(int a,int b){//扩展欧几里得 if(b==0){xx=1;yy=0;return a;} int ans=ex_gcd(b,a%b); int t=xx;xx=yy;yy=t-a/b*yy; return ans; } signed main(){ cin>>x>>y>>m>>n>>l; int S=x-y,W=n-m; if(W<0){W=-W,S=-S;} int gcd=ex_gcd(W,l); int mod=l/gcd; if(S%gcd!=0) cout<<"Impossible"<<endl; else cout<<(xx*(S/gcd)%mod+mod)%mod<<endl; return 0; }
这个题的范围很小,可以直接从小到大枚举答案。
枚举两个野人,求出他们在哪一年碰面,得到线性方程如下:
转换一下
于是这里就跟上面那个题一样了,只是需要判断次。
#include<cstdio> #include<iostream> #include<cmath> #include<cctype> #define Max(a,b) a=max(a,b) using namespace std; inline int read(){ int s=0;bool flag=true;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')flag=false;ch=getchar();} while(isdigit(ch)){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return flag?s:-s; } const int MAXl=1e6; const int N=20; int n,maxl,x,y; int c[N],p[N],l[N]; inline int ex_gcd(int a,int b){ if(!b){x=1;y=0;return a;} int ans=ex_gcd(b,a%b); int t=x;x=y;y=t-a/b*y; return ans; } inline bool check(int i,int j,int len){ int A=p[j]-p[i],B=c[i]-c[j]; if(A<0){A=-A,B=-B;} int gcd=ex_gcd(A,len); int mod=len/gcd; if(B%gcd!=0) return false; if((x*(B/gcd)%mod+mod)%mod <= min(l[i],l[j])) return true; return false; } signed main(void){ n=read(); for(int i=1;i<=n;i++) c[i]=read(),p[i]=read(),l[i]=read(),Max(maxl,c[i]); for(int i=maxl;i<=MAXl;i++){ bool flag=false; for(int j=1;j<n;j++){ for(int k=j+1;k<=n;k++) if(flag=check(j,k,i)) break; if (flag) break; } if (!flag){cout<<i<<endl;return 0;} } }
3.求解逆元
存在x使得ax≡1(mod p),则称x是a关于p的乘法逆元
a关于p的乘法逆元存在的充要条件是gcd(a,p)=1,即a,p互质
如果逆元存在,则在 0 到 p − 1 范围内有且仅有一个逆元。
当要求(a/b)%p时,且a很大,我们就求b关于p的乘法逆元x,则有(a/b)%p = (a*x)%p
求逆元就是用扩欧的,下面是纯板子:
#include<cstdio> #include<iostream> #include<cmath> #include<cctype> using namespace std; inline int read(){ int s=0;bool flag=true;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')flag=false;ch=getchar();} while(isdigit(ch)){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return flag?s:-s; } inline void out_put(int x){ if(x<0) x=-x,putchar('-'); if(x>9) out_put(x/10); putchar(x%10+'0'); } inline void print(int x){out_put(x),puts("");} int a,b,x,y; inline void ex_gcd(int a,int b){//扩展欧几里得 if(b==0){x=1;y=0;return ;} ex_gcd(b,a%b); int t=x;x=y;y=t-a/b*y; } signed main(){ a=read(),b=read(); ex_gcd(a,b); print((x+b)%b);//保证实际意义,可能为负数 return 0; }
这种方法只能在p为质数时才能用
我这里解释一下:
已知
设
可得
两边同时乘和得:
就会得到如下的线性公式:
inv[1]=1; for(int i=2;i<=n;++i) inv[i]=(p-p/i)*inv[p%i]%p;
代码就很简单了:
#include<cstdio> #include<iostream> using namespace std; inline void out_put(int x){ if(x<0) x=-x,putchar('-'); if(x>9) out_put(x/10); putchar(x%10+'0'); } inline void print(int x){out_put(x),puts("");} int main(){ int n,p;cin>>n>>p; long long inv[n+5]; inv[1]=1;cout<<1<<endl; for(int i=2;i<=n;++i){ inv[i]=(p-p/i)*inv[p%i]%p; print(inv[i]); } return 0; }
p=iq