红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【数学】逆元

逆元

前置知识

单位元

在一个集合中,对于某种运算,如果对于任何的集合元素 a,和元素 e 运算,得到还是集合元素 a 本身,则称 e 为这个运算下的单位元。

在加法运算中,对于任意实数\(a\),有\(a+e=e+a=a\),则单位元\(e=0\) (可以认作相反数)

在乘法运算中,对于任意实数\(a\),有\(a\times e=e\times a=a\),则单位元\(e=1\) (可以认作倒数)

模乘的单位元是\(1(\bmod n)\) (证明自己搜)

逆元

在一个集合中,对于某种运算 ,如果任意两个元素的运算结果等于单位元,则称这两个元素互为逆元。

模意义下的乘法逆元

\(ax\equiv 1(\bmod p)\)\(\gcd(a,p)=1\)(a与p互质),则称a关于模p的乘法逆元为x

逆元即数论中的广义倒数。

费马小定理

若p为质数,则\(\gcd(a,p)=1\),则\(a^{p-1}\equiv 1(\bmod p)\)

或对于任意整数a,有\(a^p\equiv a(\bmod p)\)

欧拉函数

\(\varphi(n)\),表示小于等于n的正整数中和n互质的数的个数,如\(\varphi(1)=1\)

  • 当n是质数时,有\(\varphi(n)=n-1\)

  • 欧拉函数是积性函数,即对任意满足\(\gcd(a,b)=1\)的整数a,b,有\(\varphi(ab)=\varphi(a)\varphi(b)\)

    特别地,当n是奇数时\(\varphi(2n)=\varphi(n)\)

求一个数的欧拉函数值:在质因数分解的同时求解即可

1
2
3
4
5
6
7
8
9
10
11
int eular_phi(int n){
    int ans = n;
    for (int i = 2;i*i<=n;i++){
        if (n%i==0){
            ans = ans/i*(i-1);
            while(n%i==0) n/=i;
        } 
    }
    if (n>1) ans = ans/n*(n-1);
    return ans;
}

欧拉定理

\(\gcd(a,m)=1\),则\(a^{\varphi(m)}\equiv 1(\bmod m)\)

则对于费马小定理,有更一般的结论:\(x\equiv a^{\varphi(p)-1}(\bmod p)\)

应用

求除法的模运算时,经常会因为精度或溢出问题而导致结果产生误差。

故我们可以将求\(\frac{a}{b}\bmod p\)转化为\(a\cdot b的逆元 \bmod p\)

将除法转为乘法,精度问题便就解决了

扩展欧几里得定理求逆元

给定正整数a,b,求满足\(ax+by=1\)的x的最小正整数解,若无解返回-1

结论

  • 当a与b不互质时,逆元必定不存在
  • 当a与b互质时,有\(ax+by=ay'+b(x'-\lfloor \frac{a}{b}\rfloor y')\)

对应系数可得: \[ \left\{ \begin{array}{**lr**} x=y' \\ y=x'-\lfloor\frac{a}{b}\rfloor y' \end{array} \right. \] 若改为求满足\(ax\equiv 1(\bmod p)\) 的最小正整数解,经过变形

原式可以由\(ax=kn+1\)变为\(ax+kn=1\),则和上方一致

模板

1
2
3
4
5
6
7
8
void exgcd(int a, int b, int &x, int &y){
    if (b==0){
        x=1,y=0;
        return ;
    }
    exgcd(b, a%b, y, x);
    y -= a/b*x;
}

费马小定理

给定素数p和正整数a,求满足\(ax\equiv 1(\bmod p)\)的最小正整数x,若不存在返回-1

此时模数固定为素数,故直接可以用费马小定理求解

当a为p的倍数时,\(ax\equiv0(\bmod p)\),所以一定不存在,返回-1

根据费马小定理有\(a^{p-1}\equiv 1(\bmod p)\),又\(ax\equiv 1(\bmod p)\)

\(ax\equiv a^{p-1}(\bmod p)\),逆元x满足\(x=a^{p-2}(\bmod p)\)

代码

1
2
3
4
5
6
7
8
9
10
int qpow(long long a, int b){
    int ans = 1;
    a = (a%p+p)%p;
    while(b!=0){
        if (b&1) ans = (a*ans)%p;
        a = (a*a)%p;
        b>>=1;
    }
    return ans;
}

线性求逆元

对于求一连串数字模p的逆元,上两种方法容易超时,用这种方法更快一些

结论

(证明见oi-wiki)

有递推式 \[ i^{-1} \equiv \left\{ \begin{array}{**lr**} 1 & i=1\\ -\lfloor\frac{p}{i}\rfloor(p\bmod i)^{-1} & i\neq1 \end{array} \right. \]

代码

1
2
3
4
5
6
void liner(ll n, ll p){
  inv[1]=1;
  for (ll i = 2;i<=n;i++){
    inv[i]=(ll)(p-p/i)*inv[p%i]%p;
  }
}

线性求任意逆元

首先我们求出n个数的前缀积\(s_{i}\),这时我们可以用exgcd或费马小定理求出\(s_{n}\)的乘法逆元

又因为\((a\times b)^{-1}=a^{-1}\times b^{-1}\)\(a\times a^{-1}=1\),故我们可以将\(s_{n}\times a_{n}\)\(a_{n}\)就会和其逆元抵消,以求出\(s_{inv_{n-1}}\)

求出所有前缀积的逆元后,我们可以让前缀积的逆元乘i-1的前缀积来消除其他逆元,以得到\(a_{i}\)的逆元,即\(s_{i-1}\times sv_{i}=a^{-1}_{i}\)

1
2
3
4
5
s[0]=1;
for (int i = 1;i<=n;i++) s[i]=s[i-1]*a[i]%p;
sv[n]=qpow(s[n],p-2); // 也可以用exgcd
for (int i = n;i>=1;i--) sv[i-1]=sv[i]*a[i]%p;
for (int i = 1;i<=n;i++) inv[i]=sv[i]*s[i-1]%p;