乘法逆元

逆元

例如一个数 \(a\),将 \(a\) 乘上 \(b\),会对 \(a\) 产生一部分的贡献,我们可以通过乘上某个数 \(x\) 来消除对 \(a\) 的贡献,那么 \(x\) 就是逆元。

乘法逆元

对于一个线性同余方程 \(ax \equiv 1 \pmod{b}\),我们称 \(x\)\(a\)\(\bmod\ b\) 意义下的逆元,也可以记为 \(a^{-1}\).

求解逆元

扩展欧几里得(exgcd)

扩展欧几里得是用来求 \(ax+by=c\)\((x,y)\) 的整数解的。

而这个方程存在解的充分必要条件是 \(\gcd(a,b) \mid c\).

而扩展欧几里得算法是在欧几里得算法上延伸而来的,需要先去了解一下欧几里得算法的证明过程。

因为 \(\gcd(a,b) \mid a,b,c\),所以我们可以将 \(a,b,c\) 同除 \(\gcd(a,b)\),即原方程可化为 \(\frac{ax}{\gcd(a,b)}+\frac{by}{\gcd(a,b)}=\frac{c}{\gcd(a,b)}\),所以我们可以先求解 \(ax+by=1\)\(a \perp b\) 的情况下 \((x,y)\) 的解。

那么可以得到:

\[\begin{aligned} ax+by & =\gcd(a,b)\\ & =\gcd(b,a \bmod b)\\ &\Rightarrow bx+(a \bmod b)y\\ & = bx+(a- \lfloor\frac{a}{b} \rfloor b)y\\ & = ya+(x- \lfloor\frac{a}{b} \rfloor y)b\\ & = ay+b(x- \lfloor\frac{a}{b} \rfloor y) \end{aligned}\]

那么就可以得到 \(x\) 变成了 \(y\),而 \(y\) 变成了 \(x- \lfloor\frac{a}{b} \rfloor y\),所以我们可以利用递归来求解 \((x,y)\).

很明显,当 \(b=0\) 时(即欧几里得算法的边界条件),\(a=1\),那么可以得到 \(x=1,y=0\).

而将 \((x,y)\) 乘上 \(c\) 就是 \(ax+by=c\) 的解。

如果要求最小非负数解,我们可以将 \(x=(x \bmod b+b)\bmod b\).

而对于这个最小非负数解涉及什么不定方程解系扩展,我太菜了没看懂qwq,求教。

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

时间复杂度是 \(O(\log n)\).

快速幂求解(费马小定理)

用快速幂求解逆元实际上是运用了费马小定理。

费马小定理的定义是,若 \(p\) 为素数,且 \(\gcd(a,p)=1\),则 \(a^{p-1} \equiv 1 \pmod{p}\).

我们可以先取一个素数 \(p\),然后取一个不为 \(p\) 的倍数的数 \(a\),即满足 \(\gcd(a,p)=1\),那么我们可以构造一个序列 \(A=\{1,2,3,\dots,p-1\}\),易得 \(\forall i,\gcd(A_i,p)=1\),又因为 \(\gcd(a,p)=1\),所以 \(\gcd(A_i \times a,p)=1\),那么就有 \(\prod_{i=1}^{n}A_i \equiv \prod_{i=1}^{n}(A_i \times a) \pmod{p}\).

因为每一个 \(A_i\) 都对应了一个 \(A_i \times a\),所以设 \(f=(p-1)!\),又因为 \(p\) 为素数,所以很容易得出 \(\gcd(f,p)=1\),所以 \(f \equiv \prod_{i=1}^{p-1}(A_i \times a) \pmod{p}\),我们将这个同余式两边同乘 \(a^{p-1}\),可得 \(a^{p-1} \times f \equiv \prod_{i=1}^{p-1}A_i\),又因为 \(A=\{1,2,3,\dots,p-1\}\),所以 \(f=\prod_{i=1}^{p-1}A_i\),所以 \(a^{p-1} \equiv 1 \pmod{p}\).

那么我们回到逆元来,因为 \(ax \equiv 1 \pmod{b}\),所以根据费马小定理我们可知 \(a^{b-1} \equiv 1 \pmod{b}\),那么可得 \(ax \equiv a^{b-1} \pmod{b}\),此处是根据同余的性质得到的。就可以得到 \(x \equiv a^{b-2} \pmod{b}\),那么 \(a\)\(\bmod b\) 意义下的逆元就是 \(a^{b-2}\)

根据费马小定理,此处我们应保证 \(b\) 为素数且 \(b \nmid a\).

int quickly_pow(int a,int b) { 
	int res=1;
	int mod=b;
	a%=mod;
	while(b) {
		if(b&1) res=(res*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return res;
}

int navie(int a,int b) {
	return quickly_pow(a,b-2);
}

时间复杂度是 \(O(\log n)\).

递推式求逆元

如果我们要求 \(1,2,3,\dots,n\) 的逆元,那么运用上面两种方法的时间复杂度均为 \(O(n \log n)\),此时如果需要一种 \(O(n)\) 的算法,就可以利用递推式来求逆元。

很显然,\(1^{-1} \equiv 1 \pmod{b}\).

那么我们可以设 \(b=k \times i+r\),那么我们就可以得到 \(k \times i+r \equiv 0 \pmod{b}\).

那么我们乘上 \(i^{-1} \times r^{-1}\) 可以得到 \(k \times r^{-1} + i^{-1} \equiv 0 \pmod{b}\),即 \(i^{-1} \equiv -k \times r^{-1} \pmod{b}\).

\(i^{-1} \equiv -\lfloor \frac{b}{i}\rfloor \times (b \bmod i)^{-1} \pmod{b}\).

又因为 \(b \bmod i < i\),所以我们可以得到求逆元的递推式:

\[i^{-1} \equiv \begin{cases} 1&\text{if}\ i=1\\ -\lfloor \frac{b}{i}\rfloor \times (b \bmod i)^{-1} &\text{otherwise} \end{cases} \pmod{b}\]

void navie() {
	inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=(mod-mod/i)+inv[i-1]%mod;
	//因为在模mod的意义下进行计算,所以-mod/i=mod-mod/i 
}

线性求逆元

首先我们计算这要求逆元的 \(n\) 个数的积,我们记为 \(p_n\),然后我们可以用快速幂或扩展欧几里得计算出 \(p_n\) 的逆元,记为 \(inv_n\)

很容易得到,当我们将 \(inv_p\) 乘上 \(a_i\) 时,就和 \(a_i\) 的逆元相抵消了,那么这样我们就可以从后往前求出第一个数到前一个数的积的逆元,即 \(inv_{n-1}=inv_n \times a_{n-1}\).

那么我们就可以用 \(inv_n \times p_n\) 来求出第 \(n\) 个数的逆元。

时间复杂度 \(O(n+\log p)\).

void navie() {
	int p[N],inv_p[N];
	int a[N],inv[N];
	for(int i=1;i<=n;i++) p[i]=p[i-1]*a[i]%mod;
	inv_p[n]=quickly_pow(p[n],mod-2);
	//也可以用exgcd来求解
	for(int i=n;i>=1;i--)  inv_p[i-1]=inv_p[i]*a[i]%mod;
	for(int i=1;i<=n;i++) inv[i]=inv_p[i]*p[i-1]%mod;
}

后记

对于扩展欧几里得算法,只需保证 \(\gcd(a,b)=1\),而对于根据费马小定理用快速幂求解,还需保证 \(b\) 为素数。

对于运用递推式和线性求法,则空间复杂度更高。

posted @ 2023-02-12 18:46  Scorilon  阅读(146)  评论(0)    收藏  举报