基礎數論複習筆記

歐幾里得

gcd(a,b)=gcd(b,a%b)

拓展歐幾里得

ax0+by0=gcd(a,b)a*x_0+b*y_0=gcd(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;
}

gcd(a,b)=gcd(b,a%b)gcd(a,b)=gcd(b,a\%b)

\Rightarrowax1+by1=bx2+(a%b)y2a·x1+b·y1=b·x2+(a\%b)·y2

\Rightarrowax1+by1=bx2+(aabb)y2a·x1+b·y1=b·x2+(a-\left\lfloor\dfrac{a}{b}\right\rfloor ·b)·y2

\Rightarrowax1+by1=ay2+b(x2aby2)a·x1+b·y1=a·y2+b·(x2-\left\lfloor\dfrac{a}{b}\right\rfloor·y2)

\Rightarrowx1=y2,y1=x2aby2x1=y2,y1=x2-\left\lfloor\dfrac{a}{b}\right\rfloor·y2

x=x0+bgcdkx'=x0+\dfrac{b}{gcd}k ,, y=y0agcdky'=y0-\dfrac{a}{gcd}k

應用

1.解方程
2.求逆元
ax1(mod  p)axpk=1a·x≡1(mod\ \ p) \\ a·x-p·k=1
pp視作bbk-k視作yy
ax+by=1\Rightarrow a·x+b·y=1
解出x0x0x=x0+phx'=x0+p·h求出合適的解

int INV(int a,int p) {
    int x,y;
    exgcd(a,p,x,y);
    x=(x%p+p)%p;
    return x;
}

數論四大定理

費馬小定理

pp爲素數,gcd(a,p)=1,gcd(a,p)=1,ap11(mod  p)a^{p-1}≡1(mod\ \ p)

應用

快速冪求逆元
由上ap11(mod  p)a^{p-1}≡1(mod\ \ p)
aap21(mod  p)\Rightarrow a·a^{p-2}≡1(mod\ \ p)
a1ap2(mod  p)\Rightarrow a^{-1}≡a^{p-2}(mod\ \ p)

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);
}

歐拉定理

是費馬小定理的一般化,等考完還有想法再補補故事
gcd(a,p)=1gcd(a,p)=1,則aφ(p)1(mod  p)a^{φ(p)}≡1(mod\ \ p)
pp爲素數,φ(p)=p1φ(p)=p-1,就成了費馬小定理

威爾遜定理

(p1)! 1(mod  p)(p−1)!\ ≡−1 (mod \ \ p) \Leftrightarrowpp爲素數
一般沒什麼用吧

中國剩餘定理

爲了區分

孫子定理

{xa1(mod  m1)xa2(mod  m2)xa3(mod  m3)xan(mod  mn) \begin{cases}\\ x≡a_1(mod \ \ m_1)\\ x≡a_2(mod \ \ m_2)\\ x≡a_3(mod \ \ m_3)\\ ……\\ x≡a_n(mod \ \ m_n) \end{cases}
其中mim_i兩兩互質,求xx的解

使用構造法,令M=ΠmiM=Πm_i
求得ti(Mmi)1(mod  mi)t_i≡{\left(\dfrac{M}{m_i}\right)}^{-1} (mod \ \ m_i)
特解爲x0=aiti(Mmi)x_0=∑a_i·t_i·\left(\dfrac{M}{m_i}\right)
通解爲x=x0+kMx'=x_0+k·M

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大佬
證明就把每一個對應部分分出來就行了,易知這是最小單位。

拓展中國剩餘定理

{xa1(mod  m1)xa2(mod  m2)xa3(mod  m3)xan(mod  mn) \begin{cases}\\ x≡a_1(mod \ \ m_1)\\ x≡a_2(mod \ \ m_2)\\ x≡a_3(mod \ \ m_3)\\ ……\\ x≡a_n(mod \ \ m_n) \end{cases}
不滿足mim_i兩兩互質,求xx的解
使用逐個求解併合並的方法。

假設我們已經得到了一組特解x0x_0,如何得到一般解呢?
類比上文的孫子定理容易得到x=x0+klcm(m1,m2.....mn)x'=x_0+k·lcm(m1,m2.....m_n)

一個一個的向下解
Mi=lcm(m1,m2......mi)M_i=lcm(m_1,m_2......m_i)
假如已經得到了前ii個方程的解x=x0+kMixx0(mod  Mi)x=x_0+k·M_i\Leftrightarrow x≡x_0(mod\ \ M_i)
現在解第i+1i+1個方程,也就是再去求一組特解。
可以將問題簡化爲
{xx0(mod  Mi)xai+1(mod  mi+1) \begin{cases} x≡x_0(mod\ \ M_i)\\ x≡a_{i+1}(mod\ \ m_{i+1}) \end{cases}
可以寫成

{x=x0+k1Mix=ai+k2mi+1x0+k1Mi=ai+k2mi+1x0ai=k1Mi+k2mi+1 \begin{cases} x=x_0+k_1·M_i\\ x=a_i+k_2·m_{i+1} \end{cases}\\ \Rightarrow x_0+k_1·M_i=a_i+k_2·m_{i+1}\\ \Rightarrow x_0-a_i=-k_1·M_i+k_2·m_{i+1}
因爲k1k1的正負不重要
x0ai=k1Mi+k2mi+1x_0-a_i=k_1·M_i+k_2·m_{i+1}
直接用拓歐解出一組k1,k2k_1,k_2代入方程再更新MiM_i一直到最後就求出了方程的解。
中途用拓歐解方程時,若gcd(Mi,mi+1)x0aigcd(M_i,m_{i+1})\nmid{x0-a_i}則無解

#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:求階乘逆元反解(見下)
n!1=(n1)!1n1n!^{-1}=(n-1)!^{-1}·n^{-1}
n1=n!1((n1)!1)1=n!1(n1)!\Rightarrow n^{-1}=n!^{-1}·((n-1)!^{-1})^{-1}=n!^{-1}·(n-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:真正的線性篩逆元
p=ki+dp=k·i+d其中d=p%id=p\%i
那麼ki+d0(mod  p)k·i+d≡0(mod\ \ p)
kid\Rightarrow -k·i≡d
kii1d1di1d1\Rightarrow -k·i·i^{-1}·d^{-1}≡d·i^{-1}·d^{-1}
i1kd1\Rightarrow i^{-1}≡-k·d^{-1}
i1=(pk)d1\Rightarrow i^{-1}=(p-k)·d^{-1}

void Prepare() {
    inv[1]=1;
    for(int i=2;i<=n;i++)
        inv[i]=(p-p/i)*inv[p%i]%p;
}

線性篩階乘逆元

用定義理解,先求出(p1)!1{(p-1)!}^{-1}(可先求階乘,或直接用威爾遜定理)
pp是素數,(p1)!1mod  p)(p-1)!≡-1(mod\ \ p)
那麼(p1)!1=(p1)1{(p-1)!}^{-1}=(p-1)^{-1}直接快速冪。
或者直接算出(p1)!(p-1)!反正都要算。
逆元滿足結合律即a1b1=(ab)1a^{-1}·b^{-1}=(ab)^{-1}
那麼inv[n]=inv[n1]n1inv[n]=inv[n-1]·n^{-1}
inv[n]n=inv[n1]n1n=inv[n1]\Rightarrow inv[n]·n=inv[n-1]·n^{-1}·n=inv[n-1]

void Prepare() {
    inv[p-1]=Pow(p-1,p-2);
    for(int i=p;i>=1;i--)
        inv[i-1]=inv[i]*i%p;
}

當然也是可以先求出線性逆元再用線性逆元求出來。

計數部分

組合數線性求法

C(n,m)=C(n1,m)+C(n1,m1)C(n,m)=C(n-1,m)+C(n-1,m-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];
}

Lucas定理

C(n,m)%p=C(n/p,m/p)C(n%p,m%p)%pC(n,m)\%p=C(n/p,m/p)·C(n\%p,m\%p)\%p
可寫爲Lucas(n,m)%p=Lucas(n/p,m/p)C(n%p,m%p)%pLucas(n,m)\%p=Lucas(n/p,m/p)*C(n\%p,m\%p)\%p
1.當pp較小
預處理組合數,直接遞歸到已處理的數

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.當pp較大
預處理階乘和階乘的逆元(見上),對小的部分直接算,大的部分繼續遞歸處理

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.括號匹配(nn對括號的合法匹配數)
2.走地圖(從(0,0)(0,0)走到(n,n)(n,n)並且不越過y=xy=x的方案數)
公式:h(n)=C(2n,n)n+1h(n)=\dfrac{C(2n,n)}{n+1}
推導:在一個(n,n)(n,n)的地圖裏,從(0,0)(0,0)走到(n,n)(n,n)的方案數爲C(2n,n)C(2n,n)
要求不越過y=xy=x,那麼求越過的取反。
越過的就是一定經過y=x+1y=x+1
觀察到這樣的走法等價於從(1,1)(-1,1)走到(n,n)(n,n)的方案數(將地圖外的部分沿y=x+1y=x+1翻折回來)。
圖示
在這裏插入圖片描述

發佈了29 篇原創文章 · 獲贊 68 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章