【数论基础】线性求逆元

线性求解连续的n个逆元

线性求解n个数字的逆元,需要找到新元素的逆元同以往求解过逆元的关系。
以下面式子举例,对于要求解逆元的k,模数为p,有:
p = a k + b ( b < a , k ) p=ak + b \\ (b < a,k) p=ak+b(b<a,k)
进而有:
a k + b ≡ 0   ( m o d   p ) ak + b \equiv 0\, (mod\,p) ak+b0(modp)
两边同时乘以k-1b-1,得到:
a b − 1 + k − 1 ≡ 0   ( m o d   p ) ab^{-1} + k^{-1} \equiv 0\,(mod\,p) \\ ab1+k10(modp)

k − 1 ≡ − a b − 1   ( m o d   p ) k^{-1}\equiv-ab^{-1}\,(mod\,p) k1ab1(modp)
我们知道,
a = ⌊ p k ⌋ b = p   m o d   k a=\left \lfloor p \over k\right \rfloor \\ b=p\,mod\,k a=kpb=pmodk
因此,有
k − 1 ≡ − ⌊ p k ⌋ ( p   m o d   k ) − 1 k^{-1}\equiv - \left \lfloor p\over k\right \rfloor(p\,mod\,k)^{-1} k1kp(pmodk)1
其中,p % k是之前已经求出的逆元,同时为了避免负数,可在计算时加上一个p。
接着注意1的逆元是1即可。

inv[1] = 1;
for(int i = 2;i <= n;i++){
	inv[i] = (p - p / i) * inv[p % i] % p;
}

求解n个数字不同数字的逆元

求解n个不同数字的逆元,可以先维护一个前缀积,其最后一项是所有数字的乘积,求该项的逆元即求所有项逆元的乘积。由于逆元的特殊性质,逆元的乘积乘上其中某个元素即会消去对应的元素,因此我们可以借助前缀积来逐个迭代处理出所有数字的逆元。
表示出来,即
( ∏ i = 1 n a i ) − 1 ≡ ∏ i = 1 n a i − 1   ( m o d   p )   或   ( a 1 a 2 . . . a n ) − 1 ≡ a 1 − 1 a 2 − 1 . . . a n − 1   ( m o d   p ) (\prod_{i=1}^{n}a_i)^{-1}\equiv\prod_{i=1}^{n}a_i^{-1}\,(mod\,p) \\ \,\\或\\\,\\ (a_1a_2...a_n)^{-1}\equiv a_1^{-1}a_2^{-1}...a_n^{-1}\, (mod \,p) (i=1nai)1i=1nai1(modp)(a1a2...an)1a11a21...an1(modp)
且有
∏ i = 1 n a i − 1 ∗ a n ≡ ∏ i = 1 n − 1 a i − 1 \prod_{i=1}^{n}a_i^{-1}*a_n \equiv\prod_{i=1}^{n-1}a_i^{-1} i=1nai1ani=1n1ai1
于是便可以处理出所有元素的逆元:

/*s是前缀积,inv是逆元*/
s[1] = a[1];
/*计算前缀积*/
for(int i = 2;i <= n;i++){
	s[i] = s[i - 1] * a[i] % p;
}
/*处理所有元素乘积的逆元,使用快速幂发求解单个逆元*/
inv[n] = fpow(s[n],p - 2);
/*逆元的前缀积*/
for(int i = n - 1;i >= 1;i--){
	inv[i] = inv[i + 1] * a[i + 1] % p;
}
/*计算全部逆元*/
for(int i = 2;i <= n;i++){
	inv[i] = inv[i] * s[i - 1] % p;
}

当然,使用这种方法也可以解决上面的问题,只需要将n个数字看成是n个自然数就行,其余步骤一样:

/*s是前缀积,inv是逆元*/
s[1] = 1;
/*计算前缀积*/
for(int i = 2;i <= n;i++){
	s[i] = s[i - 1] * i % p;
}
/*处理所有元素乘积的逆元,使用快速幂发求解单个逆元*/
inv[n] = fpow(s[n],p - 2);
/*逆元的前缀积*/
for(int i = n - 1;i >= 1;i--){
	inv[i] = inv[i + 1] * (i + 1) % p;
}
/*计算全部逆元*/
for(int i = 2;i <= n;i++){
	inv[i] = inv[i] * s[i - 1] % p;
}

例题

这里有两道例题:

乘法逆元

第一道就是线性求逆元的模板题,套用上面的式子即可直接获得答案

#include
using namespace std;
const int N = 3e6 + 50;
long long inv[N];
int n,p;
int main(){
	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] << endl;
	}	
}

或者

#include
#include
using namespace std;
const int N = 2e7 + 50;
int n,p;
long long inv[N];
long long s[N];
long long fpow(long long k,long long po){
	long long ans = 1;
	for(;po;po >>= 1,k = k * k % p){
		ans = po & 1 ? ans * k % p : ans;
	}
	return ans;
}
int main(){
	cin >> n >> p;
	s[1] = 1;
	for(int i = 2;i <= n;i++){
		s[i] = s[i - 1] * i % p;
	}
	inv[n] = fpow(s[n],p - 2);
	for(int i = n - 1;i >= 1;i--){
		inv[i] = inv[i + 1] * (i + 1) % p;
	}
	for(int i = 2;i <= n;i++){
		inv[i] = inv[i] * s[i - 1] % p;
	}
	for(int i = 1;i <= n;i++){
		printf("%d\n",inv[i]);
	}
}

乘法逆元2

这个题就是第二个模板的问题,最后统计答案的时候可以使用秦九韶算法简化统计过程(也就是倒过来一边加一边乘):

#include
#include
using namespace std;

const long long p = 1e9 + 7;
const long long m = 998244353;
const int N = 1e7 + 50;

long long fpow(long long k,long long po){
	long long ans = 1;
	for(;po;po >>= 1,k = k * k % p){
		ans = po & 1 ? ans * k % p : ans;
	}
	return ans;
}

long long a[N];
long long s[N];
long long inv[N];
long long minv;
int n;

int main(){
	cin >> n;
	for(int i = 1;i <= n;i++){
		scanf("%lld",&a[i]);
	}
	s[1] = a[1];
	for(int i = 2;i <= n;i++){
		s[i] = a[i] * s[i - 1] % p;
	}
	inv[n] = fpow(s[n],p - 2);
	for(int i = n - 1;i >= 1;i--){
		inv[i] = inv[i + 1] * a[i + 1] % p;
	}
	for(int i = 2;i <= n;i++){
		inv[i] = inv[i] * s[i - 1] % p;
	}
	minv = 1;
	long long ans = 0;
	for(int i = 1;i <= n;i++){
		ans = ans * m % p;
		ans = (ans + inv[i]) % p;
	}
	cout << ans;
}

你可能感兴趣的:(算法,算法,数学建模,乘法逆元,逆元,数论)