乘法逆元学习笔记

一、乘法逆元

如线性同余方程 a x ≡ 1 ( m o d b ) ax\equiv 1\pmod b ax1(modb) 的解 x x x 称作 a a a 在模 b b b 下的逆元,记作 x = a − 1 x=a^{-1} x=a1

二、乘法逆元存在的条件

根据线性同余方程组解存在的条件,当且仅当 gcd ⁡ ( a , b ) = 1 \gcd(a,b) =1 gcd(a,b)=1 时, a a a 在模 b b b 下存在逆元。

三、求解逆元

1. 扩展欧几里得算法

使用扩展欧几里得算法可以在 O ( log ⁡ b ) O(\log b) O(logb) 的时间复杂度下求出单个数的逆元。
求解线性同余方程 a x ≡ 1 ( m o d b ) ax\equiv 1\pmod b ax1(modb) 即可。

void exgcd(long long a, long long b, long long &x, long long &y){
	if (!b){
		x = 1, y = 0;
		return ;
	}
	exgcd(b, a % b, y, x);
	y -= (a / b) * x;
}
long long get_inv(long long a, long long p){
	long long x, y;
	exgcd(a, p, x, y);
	if (x < 0) x += p;
	return x;
}

2. 快速幂

使用快速幂可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度下求出单个数的逆元(模数应为质数)。
b b b 为质数时,由费马小定理得 a b − 1 ≡ 1 ( m o d b ) a^{b-1}\equiv 1 \pmod b ab11(modb) ,因此 a × a b − 2 ≡ 1 ( m o d b ) a \times a^{b-2} \equiv 1 \pmod b a×ab21(modb) ,即 a a a 的逆元为 a b − 2 a^{b-2} ab2 。代码如下:

long long qPow(long long a, long long b){
	long long ret = 1;
	for (; b; b >>= 1){
		if (b & 1) ret = (ret * a) % p;
		a = (a * a) % p;
	}
	return ret;
}

3. 线性求逆元

使用线性求逆元的方法可以在 O ( b ) O(b) O(b) 的时间复杂度下求出 1 1 1 ~ b − 1 b-1 b1 所有数在模 b b b 下的逆元。
1 1 1 b − 1 b-1 b1 递推。
1 1 1 的逆元为 1 1 1
② 设当前数为 i i i ,且 b = k i + r ( r < i ) b=ki+r(r<i) b=ki+r(r<i) ,则 b   m o d   i = r b\bmod i = r bmodi=r
在模 b b b 意义下:
k i + r ≡ 0 ( m o d b ) ki+r\equiv 0\pmod b ki+r0(modb)

两边同时乘以 i − 1 r − 1 i^{-1}r^{-1} i1r1 得:
k r − 1 + i − 1 ≡ 0 ( m o d b ) kr^{-1}+i^{-1}\equiv0\pmod b kr1+i10(modb)

移项整理:
i − 1 ≡ − k r − 1 ≡ − ⌊ b i ⌋ ( b   m o d   i ) − 1 ( m o d b ) i^{-1}\equiv -kr^{-1}\equiv -\lfloor\frac{b}{i}\rfloor (b\bmod i)^{-1}\pmod b i1kr1ib(bmodi)1(modb)

由于 b   m o d   i < i b\bmod i < i bmodi<i ,因此已知,可以 O ( 1 ) O(1) O(1) 递推。代码如下(Luogu 3811 【模板】 乘法逆元):

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 5;
int n, p;
int inv[maxn];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> p;
	inv[1] = 1;
	for (int i = 2; i <= n; i ++) inv[i] = (p - (p / i)) * inv[p % i] % p;
	for (int i = 1; i <= n; i ++) cout << inv[i] << '\n';
	return 0;
}

4. 线性求逆元(任意)

接下来介绍一种可以在几乎线性的时间内求出给出数列 { a n } \{a_n\} {an} 中所有数在模 b b b 意义下的逆元的方法。
首先计算 { a i } \{a_i\} {ai} 的前缀积,记为 { s u m n } \{sum_n\} {sumn} ,即 s u m i = Π j = 1 i a j sum_i=\Pi_{j=1}^{i} a_j sumi=Πj=1iaj 。其次计算 s u m n sum_n sumn 的逆元,记为 i n v s n invs_n invsn 。将 i n v s n invs_n invsn 倒推求出每一个前缀积的逆,记作 { i n v s n } \{invs_n\} {invsn} ,递推公式为:
i n v s i − 1 = i n v s i ∗ a i invs_{i-1} = invs_{i} * a_i invsi1=invsiai

有了两个数组,就可以据此得出所有数的逆元 { i n v i } \{inv_i\} {invi}
i n v i = i n v s i − 1 × s u m i inv_i=invs_{i-1}\times sum_i invi=invsi1×sumi

代码如下(Luogu 5431 【模板】 乘法逆元2):

/*
 提示:题目卡常,需要使用快读
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 5;

long long n, p, k;
long long a[maxn], sum[maxn], invs[maxn];

template <typename T>
inline void read(T &x){
    char c;
    x = 0;
    int fu = 1;
    c = getchar();
    while(c > 57 || c < 48){
        if(c == 45) fu = -1;
        c = getchar();
    }
    while(c <= 57 && c >= 48){
        x = (x << 3) + (x << 1) + c - 48;
        c = getchar();
    }
    x *= fu;
}

long long qPow(long long a, long long b){
	long long ret = 1;
	for (; b; b >>= 1){
		if (b & 1) ret = (ret * a) % p;
		a = (a * a) % p;
	}
	return ret;
}

int main(){
	read(n), read(p), read(k);
	for (int i = 1; i <= n; i ++) read(a[i]);
	sum[0] = 1;
	for (int i = 1; i <= n; i ++) sum[i] = sum[i - 1] * a[i] % p;
	invs[n] = qPow(sum[n], p - 2);
	for (int i = n - 1; i >= 1; i --) invs[i] = invs[i + 1] * a[i + 1] % p;
	long long mulk = 1, ans = 0;
	for (int i = 1; i <= n; i ++){
		mulk = mulk * k % p;
		ans = (ans + mulk * (invs[i] * sum[i - 1] % p) % p) % p;
	}
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值