前置知識
模運算:取餘運算,即a除以b得到的餘數,記為mod,又記為%
模運算過程中,加減乘都可以先對a,b進行%p,然後再進行加減乘,最後再%p,結果不變
運算子優先順序,模運算和乘除法的運算子優先順序是一樣的
同餘:a和b除以p得到的餘數相同,即p可以整除(a-b)
乘法逆元的問題背景
求解\(\cfrac{a}{b} \mod p\)的值,因為除法不能直接對a,b先取模再進相除,所以這邊就引入了逆元
逆元,可以理解為是在mod p意義下b的倒數,下文中將b的逆元記錄為inv[b]
以下是逆元的定義
\(b \times inv[b] \equiv 1 \pmod p\)
逆元的性質及其證明
1.第一個性質
\(\cfrac{a}{b} \equiv a \times inv[b] \pmod p\)
證明如下
\(b \times inv[b] \equiv 1 \pmod p\)
\((b \times inv[b]-1)\bmod p=0\)
由模運算的乘法性質\(a \times b \bmod p=(a \bmod p) \times (b \bmod p)\bmod p\)
得到\(\cfrac{a}{b}\times(b \times inv[b]-1)\bmod p=0\)
\((a \times inv[b]- \cfrac{a}{b})\bmod p=0\)
\(a \times inv[b] \equiv \cfrac{a}{b} \pmod p\)
2.唯一性
設b有兩個逆元c,d
則\(a*c \equiv a*d \pmod p\)
\((a*c-a*d)\pmod p=0\)
\(a(c-d) \mod p=0\)
\(a\% p \neq 0\)
由模運算的乘性質得到\(c=d\)
所以b的逆元唯一
3.可積性
\(inv[a] \times inv[b]=inv[a \times b]\)
證明
\(a*b * inv[ab]\equiv \pmod p\)
又因為\(a*inv[a]\equiv 1\)
\(b*inv[b]\equiv 1\)
模運算乘性質易證可積性成立
4.週期性
求解\(\cfrac{a}{b} \mod p\)中b的逆元
一般預設b小於p,但是當b大於p的時候來求逆元,其實就是一個週期性
即\(inv[k*p+r]=inv[r],0<r<p,k \in N^*\)
證明
因為同餘,滿足兩邊同乘以一個數依舊成立
所以有以下
設\(\cfrac{n}{r} \equiv k_1 \pmod p\)
\(\cfrac{n}{tp+r} \equiv k_2 \pmod p\)
則對於第二個式子,兩邊同時乘以\(tp+r\)有
\(n \equiv k_2r \pmod p\)
\(\cfrac{n}{r} \equiv k_2 \pmod p\)
同理\(\cfrac{n}{r} \equiv k_1 \pmod p\)
即\(k_1 \equiv k_2 \pmod p\)
設\(b=r,c=t*p+r\),則有
\(\cfrac{n}{b} \equiv \cfrac{n}{c} \pmod p\)
\(n*inv[b] \equiv n*inv[c] \pmod p\)
\(inv[b] \equiv inv[c] \pmod p\)
又因為\(0\le inv[b],inv[c] <p\)
所以\(inv[b]=inv[c]\)
逆元求法
費馬小定理和擴充歐幾里得這邊就不講了
這邊主要講線性求逆元
一下模的結果範圍為[0,r-1]
\(p=k*i+r\)
\(k*i+r \equiv 0 \pmod p\)
模運算乘性質有
式子兩邊同時乘以\(inv[i]*inv[r]\)有
\(k*inv[r]+inv[i] \equiv 0 \pmod p\)
\(inv[i] \equiv k*inv[r] \pmod p\)
\(inv[i] \equiv -\cfrac{p}{i}*inv[p \% i] \pmod p\)
其中p%i在[0,i-1]範圍內,即求i的時候p%i的逆元已經被求出
1.線性求逆元
原題見,洛谷P3811乘法逆元模版題
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx=20000999;
ll inv[mx];
void getinv(ll n,ll p){
inv[1]=1;
for(int i=2;i<=n;i++){
inv[i]=(1ll*(p-(p/i))*inv[p%i])%p;
//因為C++對於負數取模還是負數所以需要+p
//中間相乘可能會爆int範圍,所以用ll
}
}
int main(){
int n,p;
scanf("%d%d",&n,&p);
getinv(n,p);
for(int i=1;i<=n;i++){
printf("%d\n",inv[i]);
}
}
2.線性求單個逆元
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx=20000999;
ll inv[mx];
ll getinv(int x,int p) {
return x==1?1:1ll*(p-p/x)*getinv(p%x,p)%p;
}
int main() {
int n,p;
scanf("%d%d",&n,&p);
cout<<getinv(n,p);
}