目錄
歐幾里得
gcd(a,b)=gcd(b,a%b)
拓展歐幾里得
int exgcd(int a,int b,int &x,int &y) {
if(b==0) {
x=1,y=0;
return a;
}
int x2,y2;
int ret=exgcd(b,a%b,x2,y2);
x=y2;
y=x2-(a/b)*y2;
return ret;
}
應用
1.解方程
2.求逆元
將視作,視作
解出用求出合適的解
int INV(int a,int p) {
int x,y;
exgcd(a,p,x,y);
x=(x%p+p)%p;
return x;
}
數論四大定理
費馬小定理
爲素數,則
應用
快速冪求逆元
由上
int mod;
LL Pow(int a,int k) {
LL base=a,ret=1;
while(k) {
if(k%2)
ret=(LL)(base*ret)%mod;
base=(LL)(base*base)%mod;
k/=2;
}
return ret;
}
LL INV(int a,int b) {
mod=b;
return Pow(a,b-2);
}
歐拉定理
是費馬小定理的一般化,等考完還有想法再補補故事
,則
當爲素數,,就成了費馬小定理
威爾遜定理
爲素數
一般沒什麼用吧
中國剩餘定理
爲了區分
孫子定理
其中兩兩互質,求的解
使用構造法,令
求得
特解爲
通解爲
LL Exgcd(LL a,LL b,LL &x,LL &y){
if(!b){x=1,y=0;return a;}
LL g=Exgcd(b,a%b,y,x);
y-=(a/b)*x;
return g;
}
int n;
LL a[MAXN+5],m[MAXN+5];
LL Mul(LL x,LL y,LL p){
if(y<0) x=-x,y=-y;
LL ret=0;
while(y){
if(y&1) ret=(ret+x)%p;
x=(x<<1)%p,y>>=1;
}
return ret;
}
LL ExCRT(){
LL M=m[1],x0=a[1],x,y,tmp;
for(int i=2;i<=n;i++){
LL g=Exgcd(M,m[i],x,y);
if((a[i]-x0)%g) return -1;
tmp=m[i]/g,x=Mul(x,(a[i]-x0)/g,tmp),x=(x+tmp)%tmp;
x0+=M*x,M=M/g*m[i],x0%=M;
}
return x0;
}
該部分感謝LSK大佬
證明就把每一個對應部分分出來就行了,易知這是最小單位。
拓展中國剩餘定理
不滿足兩兩互質,求的解
使用逐個求解併合並的方法。
假設我們已經得到了一組特解,如何得到一般解呢?
類比上文的孫子定理容易得到
一個一個的向下解
令
假如已經得到了前個方程的解
現在解第個方程,也就是再去求一組特解。
可以將問題簡化爲
可以寫成
因爲的正負不重要
直接用拓歐解出一組代入方程再更新一直到最後就求出了方程的解。
中途用拓歐解方程時,若則無解
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=int(1e5+5);
int n,M=1,x0=0;
int a[MAXN],m[MAXN];
int exgcd(int a,int b,int &x,int &y) {
if(b==0) {
x=1,y=0;
return a;
}
int x2,y2;
int ret=exgcd(b,a%b,x2,y2);
x=y2;
y=x2-(a/b)*y2;
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&m[i],&a[i]);
for(int i=1;i<=n;i++) {
int x,y;
int G=exgcd(M,m[i],x,y);
if((x0-a[i])%G) {
puts("No solution");
return 0;
}
x*=(x0-a[i])/G,y*=(x0-a[i])/G;
x0=a[i]+y*m[i];
M=M/G*m[i];
x0=((x0%M)+M)%M;
}
printf("%d",x0);
}
逆元
拓歐求逆元
見上
費馬小定理求逆元
見上
線性篩逆元
法1:求階乘逆元反解(見下)
int Pow(int a,int k) {
int base=a,ret=1;
while(k) {
if(k%2)
ret=((LL)ret*base)%p;
base=((LL)base*base)%p;
k/=2;
}
return ret;
}
void Prepare() {
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=((LL)fac[i-1]*i)%p;
inv1[n]=Pow(fac[n],p-2);
for(int i=n;i>=1;i--)
inv1[i-1]=((LL)inv1[i]*i)%p;
for(int i=1;i<=n;i++)
inv2[i]=((LL)inv1[i]*fac[i-1])%p;
}
法2:真正的線性篩逆元
若其中
那麼
void Prepare() {
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(p-p/i)*inv[p%i]%p;
}
線性篩階乘逆元
用定義理解,先求出(可先求階乘,或直接用威爾遜定理)
是素數,
那麼直接快速冪。
或者直接算出反正都要算。
逆元滿足結合律即
那麼
void Prepare() {
inv[p-1]=Pow(p-1,p-2);
for(int i=p;i>=1;i--)
inv[i-1]=inv[i]*i%p;
}
當然也是可以先求出線性逆元再用線性逆元求出來。
計數部分
組合數線性求法
void Prepare() {
C[0][0]=C[1][0]=C[1][1]=1;
for(int i=1;i<=MAXN;i++)
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
Lucas定理
可寫爲
1.當較小
預處理組合數,直接遞歸到已處理的數
void Prepare() {
C[0][0]=C[1][0]=C[1][1]=1;
for(int i=1;i<=MAXN;i++)
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
int Lucas(int n,int m) {
if(n<p&&m<p)
return C[n][m];
return Lucas(n/p,m/p)*Lucas(n%p,m%p)%p;
}
3.當較大
預處理階乘和階乘的逆元(見上),對小的部分直接算,大的部分繼續遞歸處理
void Prepare() {
inv[p-1]=Pow(p-1,p-2);
for(int i=p-1;i>=1;i--)
inv[i-1]=inv[i]*i%p;
fac[0]=1;
for(int i=1;i<=p;i++)
fac[i]=fac[i-1]*i%p;
}
int C(int n,int m) {
if(n<m)
return 0;
return ((fac[n]*inv[m])%p*inv[n-m])%p;
}
int Lucas(int n,int m) {
if(n<p&&m<p)
return C(n,m);
return Lucas(n/p,m/p)*C(n%p,m%p)%p;
}
卡特蘭數
組合意義(經典例子):
1.括號匹配(對括號的合法匹配數)
2.走地圖(從走到並且不越過的方案數)
公式:
推導:在一個的地圖裏,從走到的方案數爲
要求不越過,那麼求越過的取反。
越過的就是一定經過的
觀察到這樣的走法等價於從走到的方案數(將地圖外的部分沿翻折回來)。
圖示