逆元的求法

逆元(数论倒数)定义:如果ax≡1 (mod p),且gcd(a,p)=1(a与p互质,不互质的话x肯定不存在),则称a关于模p的乘法逆元为x,记为a^-1。(逆元不唯一)
逆元的作用:a/b ≡ a * inv(b) (mod p)
证明:a/b ≡ (a/b) * 1 ≡ (a/b) * (b * inv(b)) (模运算对加减乘可交换,对除不可交换)≡ a * inv(b) (mod p)
提到逆元就不得不提及取模运算同余式的相关性质:

  1. 取模运算只能是整数,比如要求(3/4)% p应理解为3 * inv(4) % p,另外(a ^ b) % p != a ^ (b % p) 。对于负数取模的情况。在c++中,设a%b,如果b为负数,算出的结果与b为正数算出的结果一致。如果a为负数,则先按a为正数来算,最后再加一个负号。如果a,b都为负数,按照上述两条原则来计算即可。
  2. 同余式两边可以同时乘以一个数,可以同时加或者减一个数。对于除法而言,如果除数与模数互质的话,可以两边除以一个数,如果不互质的话,可能等号仍然成立也可能不再成立。

求逆元的三种方法

  1. mod为质数,可以使用费马小定理来求,mod不为质数,可以使用欧拉定理来求
  2. 扩展欧几里得来求
  3. mod为质数时,可以线性筛出1-n的逆元

第一种方法欧拉定理
费马小定理
假如a是一个整数,p是一个质数,那么a^p - a 是p的倍数,a^p ≡a (mod p)
如果a不是p的倍数,这个定理也可以写成a^(p-1) ≡1 (mod p)。
欧拉定理
对于任何两个互质的正整数x, m(m>2)有 : x^φ(m) ≡ 1(mod m)。
(证明略)
于是有x * x ^ (φ(m) - 1) ≡ 1 (mod m),于是inv(x) = x ^ (φ(m) - 1),特别的,当m为质数时,φ(m) = m-1,此时inv(x) = x ^ (m-2)。可以使用快速幂来求。复杂度为O(log mod)

int power(int a, int b, int p) { //快速幂计算(a^b) % p
	int ans = 1 % p;
	for(; b; b >>= 1) {
		if (b & 1) ans = (ll)ans * a % p;
		a = (ll)a * a % p;
	}
	return ans;
}

int phi(int n) { //求n的欧拉函数 
	int ans = n;
	for (int i = 2; i <= n / i; i++) {
		if (n % i == 0) ans = ans / i * (i - 1);
		while (n % i == 0) n /= i;
	}
	if (n > 1) ans = ans / n * (n - 1);
	return ans;
}

第二种方法扩展欧几里得
要求ax ≡ 1 (mod b),即找一个x使得ax + by = 1,由扩展欧几里得即可求得,扩展欧几里得可能返回负值,所以要加b模b。该种方法效率最高,复杂度为min(O(loga, logb)),当模数很大,但是要求逆元的数很小的时候非常适用。
代码:

void exgcd(int a, int b, int& x, int& y) { //求x, y使得ax + by = gcd(a, b) 
	if (b == 0) {
		x = 1, y = 0;
		return;
	}
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}

int inverse(int a, int p) { //求a关于p的逆元 
	int ans, tmp;
	exgcd(a, p, ans, tmp);
	return (ans + p) % p;
}

第三种方法线性筛
这种方法只能求1-mod的逆元,大于mod的无法求,逆元不存在时返回0。
代码:

void inverse_linear_sieve(int n, int p) { //线性求出1-n关于p的逆元 
	inv[1] = 1;
	for (int i = 2; i <= n; i++) {
		inv[i] = (p - p / i) * (ll)inv[p % i] % p;
	}
}

你可能感兴趣的:(模板,算法)