线性求解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+b≡0(modp)
两边同时乘以k-1b-1,得到:
a b − 1 + k − 1 ≡ 0 ( m o d p ) ab^{-1} + k^{-1} \equiv 0\,(mod\,p) \\ ab−1+k−1≡0(modp)
即
k − 1 ≡ − a b − 1 ( m o d p ) k^{-1}\equiv-ab^{-1}\,(mod\,p) k−1≡−ab−1(modp)
我们知道,
a = ⌊ p k ⌋ b = p m o d k a=\left \lfloor p \over k\right \rfloor \\ b=p\,mod\,k a=⌊kp⌋b=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} k−1≡−⌊kp⌋(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个不同数字的逆元,可以先维护一个前缀积,其最后一项是所有数字的乘积,求该项的逆元即求所有项逆元的乘积。由于逆元的特殊性质,逆元的乘积乘上其中某个元素即会消去对应的元素,因此我们可以借助前缀积来逐个迭代处理出所有数字的逆元。
表示出来,即
( ∏ 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=1∏nai)−1≡i=1∏nai−1(modp)或(a1a2...an)−1≡a1−1a2−1...an−1(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=1∏nai−1∗an≡i=1∏n−1ai−1
于是便可以处理出所有元素的逆元:
/*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]);
}
}
这个题就是第二个模板的问题,最后统计答案的时候可以使用秦九韶算法简化统计过程(也就是倒过来一边加一边乘):
#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;
}