preparing

gcd,exgcd与逆元

gcd

最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。\(a\)\(b\)的最大公约数记为\((a,b)\),同样的,\(a\)\(b\)\(c\)的最大公约数记为\((a,b,c)\),多个整数的最大公约数也有同样的记号。求最大公约数有多种方法,常见的有质因数分解法、短除法、辗转相除法、更相减损法。与最大公约数相对应的概念是最小公倍数,\(a\)\(b\)的最小公倍数记为\([a,b]\)。 ----选自百度百科

也就是说,若有两个数\(a,b\),若存在\(x\)使得\(x\mid b\)\(x\mid a\),则称\(x\)\(a,b\)的公约数,\(a,b\)的所有公约数中,最大的即为最大公约数,记为\((a,b)\)\(\gcd(a,b)\)

(补充)相对应地,若存在\(x\)使得\(a\mid x\)\(b\mid x\),则称\(x\)\(a,b\)的公倍数,\(a,b\)的所有公倍数中,最小的即为最小公倍数,记为\([a,b]\)\(\operatorname{lcm}(a,b)\)\(\operatorname{lcm}(a,b)=\dfrac{a*b}{\gcd(a,b)}\)

\(gcd\)的方法有质因数分解法、短除法、辗转相除法、更相减损法。下面粗略讲解前两个并详细讲第三个,其余自行右转百度百科

  • 质因数分解法
    顾名思义,该方法就是将\(a\)\(b\)都分解质因数,所有公共部分的积即为\(\gcd\)
    比如,若想要求\(\gcd(24,60)\),首先将\(24\)\(60\)质因数分解得到\(24=2^3\times 3\)\(60=2^2\times 3\times 5\),公共部分即为\(\gcd(24,60)=2^2\times 3=120\)。这种做法保证了得到的数\(x\)\(a,b\)的公因数,因为\(x\)是从\(a,b\)的因数中选出若干数相乘的;同时确保\(x\)最大,因为剩余部分不存在\(a,b\)的公因数了。
  • 短除法
    这种做法和上面的做法大体相同,我们将要求\(\gcd\)的数并排写,逐次除以公约数直到商互质,这时所有除过的数的积即为\(\gcd(a,b)\)。如下图可以求出\(\gcd(24,60)\)

欧几里得算法求gcd

欧几里得算法通常称为辗转相除法。

算法内容

\(\gcd(a,b) = \gcd(b,a\bmod b)\),故可一直递归直到\(b = 0\)时,此时\(a\)为原来两数的最大公约数。如下面给出求\(\gcd(24,60)\)的过程(其实就是不断使用上述等式):

\[\gcd(24,60)=\gcd(60,24)=\gcd(24,12)=\gcd(12,0) \]

\[\therefore\ \gcd(24,60)=12 \]

证明

\(\gcd(a,b) = x\),则设\(a=px,b=qx\)(显然\(\gcd(p,q)=1\),即\(p,q\)互质)

再令 \(r = a\bmod b , k=\left\lfloor\dfrac{a}{b}\right\rfloor\),则\(r=a-kb=px-k\cdot qx=(p-kq)x\)

\(\therefore \gcd(b,a\bmod b)=\gcd(b,r)=\gcd(qx,(p-kq)x)=\gcd(q,p-kq)\cdot x\)

\(\gcd(q,p-kq)=t\),又设\(q=p't,p-kq=q't\)\(\gcd(p',q')=1\)

\(p=q't+kq=q't+k\cdot p't=(q'+kp')t\)

\(\because a=px=(q'+kp')tx,b=qx=p'tx\)

\(\therefore \gcd(a,b)=\gcd((q'+kp')tx,p'\cdot tx)=\gcd(q'+kp',p')tx\)

\(\because kp'\)\(p'\) 的倍数而 \(q'\) 不是\(\ \ \ \ \ \therefore \gcd(q'+kp',p')=1\)

\(\therefore \gcd(a,b)=\gcd(q'+kp',p')tx=tx\)

\(\because \gcd(a,b) = x\ \ \ \ \ \therefore t=\gcd(q,p-kq)=1\)

\(\therefore \gcd(b,a\bmod b)=\gcd(q,p-kq)\cdot x=x=\gcd(a,b)\),即\(\gcd(a,b)=\gcd(b,a\bmod b)\)

代码

#include<iostream>
#include<cstdio>
using namespace std;
int a,b;
int gcd(int x,int y){
	return y==0?x:gcd(y,x%y);
}
int main(){
	scanf("%d%d",&a,&b);
	printf("%d",gcd(a,b));
	return 0;
}

exgcd

\(exgcd\),即扩展欧几里得算法,也可以叫做扩欧。

扩展欧几里得算法是欧几里得算法的扩展。除了计算\(a,b\)两个整数的最大公约数,此算法还能找到整数\(x,y\)(其中一个很可能是负数)使得\(ax + by = \gcd(a,b)\)。有两个数\(a,b\),对它们进行辗转相除法,可得它们的最大公约数——这是众所周知的。然后,收集辗转相除法中产生的式子,倒回去,可以得到\(ax+by=\gcd(a,b)\)的整数解。 ----节选自百度百科

让我们看一道例题:

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

题目意思是给定两个数\(a,b\),求同余方程\(ax\equiv1\pmod{b}\)的最小正整数解。

考虑到暴力枚举\(x\)一定会超时,我们考虑用\(exgcd\)求解。
把问题转化成\(ax+by=1(y < 0)\),那么就变成了我们刚刚已知的\(ax + by = \gcd(a,b)\)形式了。

这里多证明一下方程\(ax+by=c\)有解则满足\(c\bmod \gcd(a,b)=0\),故此题中\(1\bmod \gcd(a,b)\)一定等于\(0\),即\(\gcd(a,b)=1\)
\(a=m\gcd(a,b),b=n\gcd(a,b)\),则一定有\(c=ax+by=(mx+ny)\gcd(a,b)\),即\(c\bmod \gcd(a,b)=0\)

证明

(下面是\(exgcd\)的推导过程,即方程\(ax+by=\gcd(a,b)\)的最小整数解而非\(ax+by=1\)
我们已知一组\(a,b\),想求\(ax + by = \gcd(a,b)\)\(x\)的最小正整数解,不妨设\(G=\gcd(a,b)\)
我们设两个数\(x_1,y_1\),令 \(bx_1 + (a\bmod b)y_1 = G\),则\(ax+by=bx_1 + (a\bmod b)y_1\)
\(\because a\bmod b = a - b\cdot \left\lfloor\dfrac{a}{b}\right\rfloor\ \ \ \ \ \ \therefore ax+by=bx_1+(a - b\cdot \left\lfloor\dfrac{a}{b}\right\rfloor)\cdot y_1\)
\(\therefore ax+by=bx_1+ay_1-b\cdot \left\lfloor\dfrac{a}{b}\right\rfloor\cdot y_1=ay_1+b(x_1-\left\lfloor\dfrac{a}{b}\right\rfloor\cdot y_1)\)
所以必然存在一组解: \(\begin{cases}x=y_1\\y=x_1-\left\lfloor\dfrac{a}{b}\right\rfloor\cdot y_1\end{cases}\)
于是,问题变成了如何求\(x_1,y_1\),此时我们需要求的是\(bx_1+(a\bmod b)y_1=G\)的一组正整数解,那么,我们可以类比刚才的解法,用\(x_2,y_2\)\(x_1,y_1\),再用\(x_3,y_3\)\(x_2,y_2\)……
以此类推,我们发现方程\(x\)\(y\)的系数从\(a,b\)变成\(a_1=b,b_1=a\bmod b\)再往下变,刚好就是我们用辗转相除法求\(\gcd(a,b)\)时的系数,所以,我们可以知道,到了最后一定会出现\(a_n=G,b_n=0\),此时,方程为\(Gx_n+0\cdot y_n=G\),于是必然存在解\(\begin{cases}x_n=1\\y_n=0\end{cases}\),此时\(y_n\)\(0\),是因为我们递归求\(y_i\),一旦我们的\(y_n\)\(1\)\(y_0\)将会非常大。
最后,因为我们求得的\(x\)不一定是最小正整数解,我们将其\(\bmod b\)得到最小正整数解,而且,由于\(x\)可能是负数,所以模完还应该\(+b\)\(\bmod b\)
于是,我们可以写出代码:

#include<iostream>
#include<cstdio>
using namespace std;
int a,b;
int x,y;
void exgcd(int a,int b){
	if(b==0){
		x=1;
		y=0;//y最好取0
		return;
	}
	exgcd(b,a%b);
	int xx=x;
	x=y;
	y=xx-a/b*y;//x,y记录该层的答案
	return;
}
int main(){
	scanf("%d%d",&a,&b);
	exgcd(a,b);
	x=(x%b+b)%b;
	printf("%d",x);
	return 0;
}

逆元

定义

如果一个线性同余方程\(ax\equiv1\pmod{b}\),则\(x\)称为\(a\)在模\(b\)意义下的逆元,记作\(a^{-1}\)。 ----选自\(OI\ Wiki\)

我们在一般的题目中,若使用$$((12\bmod 5)+(3\bmod 5))\bmod5=(12+3)\bmod 5=0$$或$$((12\bmod 5)\times(3\bmod 5))\bmod5=(12\times3)\bmod5=1$$是可行的,但是,若是除法则不能:$$((12\bmod 5)/(3\bmod 5))\bmod5=\dfrac{2}{3}\ne (12/3)\bmod 5=4$$
因此,我们引入逆元来解决这一问题。由定义,假设在模\(b\)意义下\(a\)的逆元为\(a^{-1}\)(即满足\(a*a^{-1}\equiv1\pmod b\)),则\(\dfrac{x}{a}\equiv x*a^{-1}\pmod1\)。证明也很简单,不妨假设\(x=k\times a\),则\(\dfrac{x}{a}=\dfrac{ka}{a}=k\),而\(x*a^{-1}=(k*a)*a^{-1}=k*(a*a^{-1})=k\)
那么,如何求一个数在模\(b\)情况下的逆元呢?

例:【模板】乘法逆元

大意

给定\(n,p\),求\(1\sim n\)在模\(p\)意义下的逆元。

解法及代码

  1. 费马小定理
    由定理,若\(a,p\)互质,则有\(a^{p-1}\equiv1\pmod p\),又因为\(a*a^{-1}\equiv1\pmod p\),故\(a*a^{-1}\equiv a^{p-1}\pmod p\),所以\(a^{-1}\equiv a^{p-2}\pmod p\),即\(a^{-1} = a^{p-2}\bmod p\)
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
ll n,p;
ll qp(ll di,ll mi){
	if(mi==0){
		return 1;
	}
	ll ans=qp(di,mi/2);
	ans=ans*ans%p;
	if(mi&1){
		ans=ans*di%p;
	}
	return ans%p;
}
int main(){
	scanf("%lld%lld",&n,&p);
	for(int i=1;i<=n;i++){
		printf("%lld\n",qp(i,p-2));
	}
	return 0;
}
  1. 欧拉定理中的数论定理
    由定理,若\(a,p\)互质,\(a^{\varphi(p)}\equiv1\pmod p\),特殊地,\(p\)是质数时\(\varphi(p)=p-1\),故式子变成\(a^{p-1}\equiv1\pmod p\),后面过程同上,代码同上(均\(TLE\))。
  2. \(exgcd\)
    因为求\(i\)在模\(p\)意义下的逆元相当是解同余方程\(a*a^{-1}\equiv1\pmod p\)的解,直接\(exgcd\)\(a^{-1}\)即可。
#include<iostream>
#include<cstdio>
using namespace std;
int a,b;
int x,y;
void exgcd(int a,int b){
	if(b==0){
		x=1;
		y=0;
		return;
	}
	exgcd(b,a%b);
	int xx=x;
	x=y;
	y=xx-a/b*y;
	return;
}
int main(){
	scanf("%d%d",&a,&b);
	for(int i=1;i<=a;i++){
		exgcd(i,b);
		x=(x%b+b)%b;
		printf("%d\n",x);
	}
	return 0;
}

理论可以AC,可是我的代码还是TLE了
4. 递推
\(k=\left\lfloor\dfrac{p}{a}\right\rfloor,r=p\bmod a\)
因为\(p=k\times a+r\),则\(k\times a+r\equiv0\pmod p\)
两边同乘\(a^{-1}*r^{-1}\)得到\(k*r^{-1}+a^{-1}\equiv0\pmod p\),移项得到\(a^{-1}\equiv-k*r^{-1}\pmod p\)
重新代入\(k,r\)得到\(a^{-1}\equiv-\left\lfloor\dfrac{p}{a}\right\rfloor*(p\bmod a)^{-1}\pmod p\)
由于\(p\bmod a<a\),所以算\(inv_a\)的时候已经算出来了\(inv_{(p\bmod a)}\)(即\((p\bmod a)^{-1}\)),又因为要保证等式右边是正数,所以我们将其加上\(p\)\(\bmod p\),这样就能将其变为正数。
综上,递推方程式如下:

\[inv[a]=p-\left\lfloor\dfrac{p}{a}\right\rfloor*inv[(p\bmod a)]\bmod p \]

初始化如下:

\[inv[1]=1,inv[0]=tan90^\circ \]

\(tan90^\circ\)即为不存在,但是我们赋值赋\(0\)

#include<iostream>
#include<cstdio>
#define maxn 3000005
#define ll long long
using namespace std;
int a,b;
ll inv[maxn];
int main(){
	scanf("%d%d",&a,&b);
	inv[1]=1; printf("%lld\n",inv[1]);
	for(ll i=2;i<=a;i++) inv[i]=(ll)b-(ll)b/i*inv[b%i]%b,printf("%lld\n",inv[i]);
	return 0;
}

例:【模板】乘法逆元 2

大意

给定 \(n,p,k\) 和长为 \(n\) 的数组 \(a\),求出 \(\sum\limits_{i=1}^n{\dfrac{k^i}{a_i}}\)

思路

小学都会的通分。
\(t = \prod\limits_{i=1}^n{a_i}\)\(pre_i\)\(las_i\) 分别为 \(a\) 的前缀积与后缀积。有:

\[\begin{aligned} \sum\limits_{i=1}^n{\dfrac{k^i}{a_i}} &= \sum\limits_{i=1}^n{\huge(\normalsize\dfrac{k^i\times\dfrac{t}{a_i}}{t}\huge)\normalsize} \\&=\sum\limits_{i=1}^n{\huge(\normalsize\dfrac{k^i\times(pre_{i-1}\times las_{i+1})}{t}\huge)\normalsize} \\&=\dfrac{\sum\limits_{i=1}^n{k^i\times(pre_{i-1}\times las_{i+1})}}{t} \end{aligned} \]

只需要求 \(t\) 的逆元即可。

#include<iostream>
#include<cstdio>
#define maxn 5000005
#define ll long long
using namespace std;
inline ll read(){
	char ch; ll res=0; bool neg=1; ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') neg=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+ch-'0';ch=getchar();} return res*neg;
}
ll n,p,k,mul=1,ans=0; ll a[maxn],pre[maxn],las[maxn];
ll inv(int xx){if(xx==1) return 1; return (1LL*p-(p/xx*inv(p%xx)%p)%p)%p;}
int main(){
	n=read(); p=read(); k=read();
	pre[0]=1LL; for(int i=1;i<=n;i++){a[i]=read();pre[i]=(pre[i-1]*a[i])%p;}
	las[n+1]=1LL; for(int i=n;i>=1;i--) las[i]=(las[i+1]*a[i])%p;
	for(int i=1,mul=k;i<=n;i++,mul=mul*k%p){ans+=(mul*(pre[i-1]*las[i+1]%p)%p)%p; ans%=p;}
	printf("%lld",ans*inv(pre[n])%p);
	return 0;
}

完结撒花

posted @ 2021-10-10 19:07  qzhwlzy  阅读(378)  评论(0编辑  收藏  举报