数!论!

逆元是个好东西,不会也要背板子。

乘法逆元

定义
ax1(modp)ax\equiv 1 \pmod p,则称 xxaa 在模 pp 意义下的乘法逆元,记作 a1a^{-1}

此时存在 x/axa1(modp)x/a\equiv xa^{-1} \pmod p。这样就可以方便的计算在模意义下的除法操作。

例:在 p=13p=13 时,3×91(mod13)3\times 9 \equiv 1 \pmod {13}。则对于任意 xx,都有 x÷39x(mod13)x\div 3\equiv 9x \pmod {13}
验证一下:27÷3=9,279=243=18×13+927\div 3 = 9, 27*9 = 243 = 18 \times 13 + 9

为什么?
为什么若 aa11(modp)aa^{-1}\equiv 1 \pmod p,则 x/axa1(modp)x/a\equiv xa^{-1} \pmod p 呢?

我们来证明一下:
x/ax/a(modp)x/a\equiv x/a \pmod p
同余式左边乘上 11,右边乘上 aa1aa^{-1}x/a×1x/a×aa1(modp)x/a \times 1 \equiv x/a\times aa^{-1} \pmod p (同余式可以逐项相乘)
所以,x/axa1(modp)x/a\equiv xa^{-1} \pmod p

同余式还有很多其他的性质,可以自行探索。

求逆元

拓展欧几里得法

算法介绍:拓展欧几里得算法

拓展欧几里得算法可以用来解同余方程。可以 O(logn)O(\log n) 求出单个数的逆元。

解方程:ax1(modp)ax\equiv 1 \pmod p
转化为 ax+py=1ax + py = 1,方程有解的条件为 gcd(a,p)1\gcd(a,p) \mid 1,即 gcd(a,p)=1\gcd(a,p)=1aapp 互质。
此时方程所有的解为 x0+kp/gcd(a,p)=x0+kp(kZ)x_0 + kp/\gcd(a,p) = x_0 + kp (k \in \Z)。注意到所有的解 xx0(modp)x \equiv x_0 \pmod p,故该线性同余方程只有一个解。(我们讨论的是在一个模剩余系中的解)。

综上我们得到了一条乘法逆元的重要性质。

乘法逆元性质 1
当且仅当 aapp 互质时,aa 在模 pp 意义下的乘法逆元 a1a^{-1} 存在,且在模剩余系意义下有且仅有一个。

需要注意的是,不要学了逆元就遇到除法就开求逆元,先注意一下模数是不是一个素数。如果模数不是素数,那么一般来说,这题就不是要用逆元求的。(为了保证所有逆元存在,题目一般设置模数为素数)

1
2
3
4
5
6
7
8
9
10
11
12
13
int exgcd(int a,int b,int& x,int& y){
if(!b){x=1,y=0;return a;}
int g=exgcd(b,a%b,x,y);
swap(x,y);
y-=x*(a/b);
return g;
}
int inv(int a,int p){ // 求 a 在模 p 意义下的逆元
if(p%a==0)return -1;
int x,y;
exgcd(a,p,x,y);
return (x+p)%p; // 保证是正数
}

线性求 1~n 逆元

在题目里,有时候需要用 1n1\sim n 之间所有数的逆元,如果每一个都用上面的方法算的话,复杂度为 O(nlogn)O(n\log n)。事实上,有一种方法,可以在 O(n)O(n) 复杂度内求出 1n1\sim n 的所有逆元。这用到的是另一个重要性质:

乘法逆元性质 2

x1{1,x=1px(pmodx)1,otherwise(modp)x^{-1} \equiv \left\{ \begin{aligned} &1, & x=1\\ &-\bigg\lfloor \dfrac{p}{x} \bigg\rfloor (p \bmod x)^{-1}, &\text{otherwise}\\ \end{aligned} \right. \pmod p

我们来证明一下:

首先 111(modp)1^{-1} \equiv 1 \pmod p

对于求 x1x^{-1}其他情况,不妨令 a=pxa = \bigg\lfloor \dfrac{p}{x} \bigg\rfloorb=pmodxb=p \bmod x
ax+b=pax+b = p,即 ax+b0(modp)ax+b\equiv 0 \pmod p
两边同乘 x1b1x^{-1}b^{-1},得 ab1+x10(modp)ab^{-1}+x^{-1}\equiv 0 \pmod p
移项,得 x1ab1(modp)x^{-1}\equiv -ab^{-1} \pmod p,即 x1px(pmodx)1(modp)x^{-1}\equiv-\bigg\lfloor \dfrac{p}{x} \bigg\rfloor (p \bmod x)^{-1}\pmod p

我们注意到 pmodxp \bmod x 小于 xx,这样就可以线性递推出所有数的逆元。代码见下。

洛谷P3811 【模板】乘法逆元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
using namespace std;

using ll=long long;
const int N = 3e6+6;

int inv[N];

int main(){
int n,p;
scanf("%d%d",&n,&p);
inv[1]=1;
for(int i=2;i<=n;i++){
inv[i]=(ll)(p-p/i)*inv[p%i]%p;
}
for(int i=1;i<=n;i++){
printf("%d\n",inv[i]);
}
return 0;
}

线性求 n 个任意数的逆元

事实上,可以在线性复杂度内求解出 nn 个任意数的逆元。(复杂度为 O(n+logp)O(n+\log p)pp 为模数)

具体方法如下:

  1. 设这 nn 个数为 a1,a2,,ana_1,a_2,\dots,a_n
  2. 分别求出前缀积 s1,s2,,sns_1,s_2,\dots,s_n
  3. 求出 sns_n 逆元 vnv_n。(用拓展欧几里得法)
  4. 对于任意 viv_ivi=vi+1ai+1v_i=v_{i+1}a_{i+1}(因为乘上一个数之后被抵消了),求出所有的 viv_i
  5. 则对于任意数 aia_i

ai1={v1,i=1visi1,otherwisea_i^{-1}=\left\{\begin{aligned}&v_1, & i=1\\&v_is_{i-1}, &\text{otherwise}\\\end{aligned}\right.

综上,即可在 O(n+logp)O(n+\log p) 复杂度求出任意 nn 个数的逆元。

洛谷P5431 【模板】乘法逆元 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<bits/stdc++.h>
using namespace std;

int read(){ // 本题卡常严重
int f=1,x=0;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}

using ll=long long;

const int N = 5e6+6;
int a[N],s[N],v[N];

int p;

int exgcd(int a,int b,int &x,int &y){
if(b==0){x=1,y=0;return a;}
int g = exgcd(b,a%b,x,y);
swap(x,y);
y-=x*(a/b);
return g;
}


int main(){


int n=read();
p=read();
int k=read();

s[0]=1;
for(int i=1;i<=n;i++){
a[i]=read()%p;
s[i]=(ll)s[i-1]*a[i]%p;
}

int x,y;
exgcd(s[n],p,x,y);
v[n] = (x+p)%p;

for(int i=n-1;i>=1;i--)v[i]=(ll)v[i+1]*a[i+1]%p;

int ans = 0;
int mul = k;
for(int i=1;i<=n;i++){
int inv;

if(i==1)inv=v[1];
else inv=(ll)v[i]*s[i-1]%p;

ans = (ans+(ll)mul*inv)%p;

mul=(ll)mul*k%p;
}

printf("%d\n",ans);

return 0;
}

总结

画一张图。