乘法逆元进阶运用

线性求逆元

\(k= \left\lfloor\dfrac{p}{i}\right\rfloor,r=p\mod i\)

\(p\equiv ik+r\equiv 0 \pmod p\)

在两边分别乘上 \(i^{-1}\)\(j^{-1}\)

得到

\((ik+r)(i^{-1}r^{-1})\equiv 0\pmod p\\ (k+ri^{-1})r^{-1}\equiv 0\pmod p\\ kr^{-1}+i^{-1}\equiv 0\pmod p\\ i^{-1}\equiv -kr^{-1}\pmod p\\ i^{-1}\equiv -\left\lfloor\dfrac{p}{i}\right\rfloor(p\mod i)^{-1}\pmod p\)

由于 \(0\le p\mod i <i\) 故递推式成立。

乘法逆元的积性——离线逆元

我们说对于乘法逆元有:\(a^{-1}\times b^{-1}\equiv (ab)^{-1}\pmod{p}\)

证明: \(a\times a^{-1}\equiv 1\pmod p\)\(b\times b^{-1}\equiv 1\pmod p\)

将两式相乘,\(a\times b\times a^{-1}\times b^{-1}\equiv 1\pmod p\),显然 \(a^{-1}\times b^{-1}\)\(ab\) 的逆元。

于是乘法逆元积性得证,我们可以用它解决一些问题。

例如

前缀积。设 \(prod(i)\) 表示从 \(1\)\(i\) 的前缀积,那么要求 \([j,i]\) 的区间积,需要 \(prod(i)/prod(j-1)\)。可是有时候乘积太大需要取模,那么这时候的除法需要乘法逆元,但是 \(prod\) 可能很大,逆元数组存不下(这里是线性逆元),这时候就要用到这条结论:前缀积的逆元,就是逆元的前缀积

\(pre_i=\prod^i_{j=1}a_j\)\(pre_i^{-1}=\prod^n_{j=1}a_j^{-1}\)

我们可以得到递推式:\(pre_i^{-1}=a_{i+1}^{-1}\times pre_{i+1}^{-1}(1\le i <n)\)

也可以求单个值 \(a_j^{-1}=pre_{j-1}\times pre_j^{-1}(1\le j \le n)\)

那么要线性求逆元,需要先求出 \(pre_n^{-1}\),可以用费马小定理(\(p\) 是质数)或Exgcd。

总时间复杂度 \(O(n+\log p)\),接近线性。

模板题:P5431【模板】乘法逆元 2

此题卡的很死,注意速度和运算空间

#include <bits/stdc++.h>
#define rei register int
#define ll long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define rep(i, s, n, c) for (register int i = s; i <= n; i+=c)
#define repd(i, s, n, c) for (register int i = s; i >= n; i-=c)
#define CHECK cout<<"WALKED"<<endl;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
#define pb push_back
#define ls id<<1
#define rs id<<1|1
const int INF = INT_MAX;
long long binpow(long long a, long long b, ll mod){long long res = 1;  while (b > 0){if (b & 1) res = res * a % mod;a = a * a % mod;  b >>= 1;  }  return res;}

using namespace std;
int a[5000006], pre[5000006], inv_pre[5000006], inv[5000006];
int main()
{
	int n; ll p, k;
	scanf("%d%lld%lld", &n, &p, &k);
	pre[0] = 1;
	for (register int i = 1; i <= n; i++) {
		a[i] = read();
		pre[i] = 1ll * pre[i - 1] * a[i] % p;
	}
	inv_pre[n] = binpow(pre[n], p - 2, p);
	for (int i = n; i > 0; i--) {
		inv[i] = 1ll * pre[i - 1] * inv_pre[i] % p;
		inv_pre[i - 1] = 1ll * inv_pre[i] * a[i] % p;
	}
	ll ans = 0;
	for (int i = 1, t = k; i <= n; i++) {
		ans = (1ll * inv[i] * t % p + ans) % p;
		t = 1ll * t * k % p;
	}
	printf("%lld\n", ans);
    return 0;
}
posted @ 2023-01-07 23:41  Vegdie  阅读(13)  评论(0编辑  收藏  举报