[自用] 數論和組合計數類數學相關(定理&證明&板子)


0 寫在前面

  本文受 NaVi_Awson 的啟發,有些地方相似,一些地方甚至直接引用,特此說明(感謝dalao)。

1 數論

  1.0 gcd

    1.0.0 gcd

      $gcd(a,b) = gcd(b,a\;mod\;b)$

     證明:設 $c\mid a$,$c\mid b$,則 $c\mid (b-a)$。

        設 $c\nmid a$,則 $c$ 不是 $a,b-a$ 的公因子。

        設 $c\mid a$,$c\nmid b$,則 $c$ 不是 $a,b-a$ 的公因子。

 

1 int gcd(int a,int b){ 2     if(!b) return a; 3     return gcd(b,a%b); 4 }

     1.0.1 exgcd

       對於一組不定方程 $a\times b+b\times y = c$。若 $gcd(a,b)\mid c$ 則這組方程有無數組解,否則無解。

       求出一組 $a\times b+b\times y = gcd(a,b) $ 的解需要 $exgcd$,是在 $gcd$ 遞歸函數過程中實現的。

       設 $x_0,y_0$ 是這一層的解,$x_1,y_1$ 是上一層的解。

       首先邊界條件:當$b=0$時顯然$x_0=1,y_0=0$。

       其他情況下有:$x_0=y_1,y_0=x_1- {\lfloor {\frac{a}{b}} \rfloor} \times y_1$。

       證明:

                       $$gcd(a,b)=x_0 \times a + y_0 \times b$$

           $$gcd(b,a\;mod\;b)=x_1 \times b + y_1 \times {(a\;mod\;b)} = x_1 \times b + y_1 \times {(a- {\lfloor {\frac{a}{b}} \rfloor)} \times b}$$

       因為                 $$gcd(a,b)=gcd(b,a\;mod\;b)$$

       得                  $$x_0 \times a + y_0 \times b =x_1 \times b + y_1 \times {(a-{\lfloor {\frac{a}{b}} \rfloor)} \times b}$$

       合並得                $$x_0 \times a+ y_0 \times b=y_1 \times a+ (x_1-{\lfloor {\frac{a}{b}} \rfloor} \times y_1) \times b$$

       根據恆等定理             $$x_0=y_1,y_0=x_1-{\lfloor {\frac{a}{b}} \rfloor} \times y_1$$

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

    upd:對於方程 $a\times x+b\times y=c$ 解出兩個特解 $x_0,y_0$ 之后,方程的通解是 $x=x_0+k\frac{b}{gcd(a,b)},y=y_0+k\frac{a}{gcd(a,b)}$。

   證明:對於兩組解 $x_0,y_0$ 和 $x_1,y_1$。顯然滿足 $ax_0+by_0=c,ax_1+by_1=c$。我們想要求通解,實際上就是想求一個 $\delta x$ 和 $\delta y$,這樣通解就是 $x_0+k\delta x$ 和 $y_0+k\delta y$。

      推一下式子: $$a(x_1-x_0)+b(y_1-y_0)=0$$

$$a\delta x+b\delta y=0$$

$$\frac{a}{gcd(a,b)}\delta x+\frac{b}{gcd(a,b)}\delta y=0$$

$$\delta x=\frac{b}{gcd(a,b)},\delta y=\frac{a}{gcd(a,b)}$$

   證畢。

 

  1.1 逆元

對於正整數 $a$ 和 $m$,如果有 $ a \times x \equiv 1 \quad (mod\;m) $,那么把這個同余方程 $x$ 中的最小正整數解叫做 $a$ 模 $m$ 的逆元。

逆元一般用擴展歐幾里得算法來求得,如果 $m$ 為素數,那么還可以根據費馬小定理得到逆元為 $a^{m-2} \equiv m$。

推導過程如下:

$$ a^{m-1} \equiv 1 \quad (mod\;m) \Rightarrow a \times a^{m-2} \equiv 1 \quad (mod\;m) \Rightarrow a^{m-2} \equiv \frac{1}{a} \quad (mod\;m)$$

另外,逆元還有線性算法:

首先,$1^{-1} \equiv 1\;(mod\;p)$

然后,我們設 $p=k \times i+r , r<i , 1<i<p$ ,再將這個式子放到 $mod\;p$ 意義下,就有: $$ k \times i + r \equiv 0 \quad (mod\;p) $$

在等號兩邊同乘上 $i^{-1},r^{-1}$,就會得到:

$$ k\times r^{-1} +i^{-1} \equiv 0 \quad (mod\;p) $$

$$ i^{-1} \equiv -k \times r^{-1} \quad (mod\;p) $$

$$ i^{-1} \equiv - {\lfloor {\frac{p}{i}} \rfloor} \times (p\;mod\;i)^{-1} \quad (mod\;p) $$

遞推代碼就是:

1 inv[i]=(p-p/i)*inv[p%i]; 2 (inv[i]+=mod)%=mod;

      因為有可能是負數,所以還要加上一個 $mod$

     1.2 中國剩余定理

    1.2.0 中國剩余定理

  在《孫子算經》中有這樣一個問題:“今有物不知其數,三三數之剩二(除以 3 余 2 ),五五數之剩三(除以 5 余3),七七數之剩二(除以 7 余 2 ),問物幾何?”這個問題被稱為“孫子問題”,該問題的一般解法國際上稱為“中國剩余定理”。具體解法分三步:

  1.找出三個數:從 3 和 5 的公倍數中找出被 7 除余 1 的最小數 15,從 3 和 7 的公倍數中找出被 5 除余 1 的最小數 21,最后從 5 和 7 的公倍數中找出除 3 余 1 的最小數 70 。

  2.用 15 乘以 2( 2 為最終結果除以 7 的余數),用 21 乘以 3(3 為最終結果除以 5 的余數),同理,用 70 除以 2(2 為最終結果除以 3 的余數),然后把三個乘積相加 $15 \times 2 + 21 \times 3 + 70 \times 2$ 得到和 233 .

  3.用 233 除以 3,5,7, 三個數的最小公倍數 105,得到余數 23,即 $233 \% 105 =23$。這個余數 23 就是符合條件的最小數。

  就這么簡單。我們在感嘆神奇的同時不禁想知道古人是如何想到這個方法的,有什么基本的數學依據嗎?

  我們將“孫子問題”拆分成幾個簡單的小問題,從零開始,試圖揣測古人是如何推導出這個解法的。

  首先,我們假設 $n_1$ 是滿足除以 3 余 2 的一個數,比如 2,5,8 等等,也就是滿足 $3 \times k +2\;(k \geq 0)$ 的一個任意數。同樣,我們假設 $n_2$ 是滿足除以 5 余 3 的一個數,$n_3$ 是滿足除以 7 余 2 的一個數。

  有了前面的假設,我們先從 $n_1$這個角度出發,已知 $n_1$ 滿足除以 3 余 2,能不能使得 $n_1+n_2$ 的和仍然滿足除以 3 余 2?進而使得  $n_1+n_2+n_3$ 的和仍然滿足除以 3 余 2?

  這就牽涉到一個最基本數學定理,如果有 $a\%b=c$,則有 $(a + k\times b)\%b = c\;(k \in N^*)$,換句話說,如果一個除法運算的余數為 $c$,那么被除數余 $k$ 倍的除數相加(或相減)的和(差)再與除數相除,余數不變。這是很好證明的。

  以此定理為依據,如果 $n_2$ 是 3 的倍數, $n_1+n_2$ 就依然滿足除以 3 余 2。同理,如果 $n_3$ 也是 3 的倍數,那么 $n_1+n_2+n_3$ 的和就滿足除以 3 余 2。這是從 $n_1$ 的角度考慮的。再從 $n_2,n_3$ 的角度出發,我們可推導出以下三點:

  1.為使 $n_1+n_2+n_3$ 的和滿足除以 3 余 2,$n_2$ 和 $n_3$ 必須是 3 的倍數。

  2.為使 $n_1+n_2+n_3$ 的和滿足除以 5 余 3,$n_1$ 和 $n_3$ 必須是 5 的倍數。

  3.為使 $n_1+n_2+n_3$ 的和滿足除以 7 余 2,$n_1$ 和 $n_2$ 必須是 7 的倍數。

  因此,為使 $n_1+n_2+n_3$ 的和作為“孫子問題”的一個最終解,需滿足:

  1. $n_1$ 除以 3 余 2,且是 5 和 7 的公倍數。

  2. $n_2$ 除以 5 余 3,且是 3 和 7 的公倍數。

  3. $n_3$ 除以 7 余 2,且是 3 和 5 的公倍數。

  所以,孫子問題解法的本質是從 5 和 7 的公倍數中找一個除以 3 余 2 的數 $n_1$,從 3 和 7 的公倍數中找一個除以 5 余 3 的數 $n_2$,從 3 和 5 的公倍數中找一個除以 7 余 2 的數 $n_3$,再將三個數相加得到解。在求 $n_1,n_2,n_3$ 時又使用了一個小技巧,以 $n_1$ 為例,並非從 5 和 7 的公倍數中直接找一個除以 3 余 2 的數,而是先找一個除以 3 余 1 的數,再乘以 2。也就是先求出 5 和 7 的公倍數模 3 下的逆元,再用逆元去乘余數。

  這里又有一個數學公式,如果 $a\%b=c$,那么 $$(a \times k) \%b =(a\%b + a\%b + a\%b + ... + a\%b)\%b = (c + c + c +...+ c)\%b = (k \times c) \%b\;(k>0)$$.也就是說,如果一個除法的余數為 $c$,那么被除數的 $k$ 倍與除數相除的余數為 $(k \times c) \%b$。展開式中已證明。

  最后,我們還要清楚一點,$n_1+n_2+n_3$ 只是問題的一個解,並不是最小的解。如何得到最小解?我們只需要從中最大限度的減掉 3,5,7 的公倍數 105 即可。道理就是前面講過的定理“如果 $a\%b=c$,則有 $(a - k \times b) \%b =c $”.所以 $(n_1+n_2+n_3)\%105$ 就是最終的最小解。 

      這樣一來就得到了中國剩余定理的公式:

設正整數$m_1,m_2,...,m_k$兩兩互素,則同余方程組

$$x \equiv a_1 \quad (mod\;m_1)$$

$$x \equiv a_2 \quad (mod\;m_2)$$

$$x \equiv a_3 \quad (mod\;m_3)$$

$$.$$

$$.$$

$$.$$

$$x \equiv a_k \quad (mod\;m_k)$$

有整數解。並且在模$M=m_1 \times m_2 \times ... \times m_k$下的解是唯一的,解為

$$x \equiv (a_1M_1M_1^{-1} + a_2M_2M_2^{-1} + ... + a_kM_kM_k^{-1}) \quad (mod\;M)$$

其中$M_i = M/m_i$,而$M_i^{-1}$為$M_i$模$m_i$的逆元。

 

 1 int exgcd(int a,int b,int &x,int &y){  2     if(!b){  3         x=1;y=0;  4         return a;  5  }  6     int c=exgcd(b,a%b,x,y);  7     int t=x;  8     x=y;  9     y=t-a/b*y; 10     return c; 11 } 12 
13 int inv(int a,int b){ 14     int x,y; 15  exgcd(a,b,x,y); 16     return (x%b+b)%b; 17 } 18 
19 int CRT(){ 20     int M=1,ans=0; 21     for(int i=1;i<=n;i++) M*=m[i]; 22     for(int i=1;i<=n;i++) 23         (ans+=(M/m[i])*inv(M/m[i],m[i])*a[i])%=M; 24     return ans; 25 }

    1.2.1 擴展中國剩余定理

普通的中國剩余定理要求所有的 $m_i$ 互質,那么如果不互質呢,怎么求解同余方程組?

這種情況就采用兩兩合並的思想,假設要合並如下兩個方程:

$$x = r_1 + m_1 \times x_1$$

$$x = r_2 + m_2 \times x_2$$

那么得到:

$$r_1 + m_1 \times x_1 = a_2 + m_2 \times x_2 \Rightarrow m_1 \times x_1 + m_2 \times x_2 = r_2 - r_1$$

我們要求出一個最小的 $x$ 使它滿足:

$$x = r_1 + m_1 \times x_1 = r_2 + m_2 \times x_2$$

那么 $x_1$ 和 $x_2$ 就要盡可能的小,於是我們用擴展歐幾里得算法求出 $x_1$ 的最小正整數解,將它代回 $a_1 + m_1 \times x_1$,得到 $x$ 的一個特解 $x'$,當然也是最小正整數解。

所以 $x$ 的通解一定是 $x'$ 加上 $lcm(m_1,m_2) \times k$($k$ 是一個常數),這樣才能保證 $x$ 模 $m_1$ 和 $m_2$ 的余數是 $r_1$ 和 $r_2$。由此,我們把這個 $x'$ 當做新的方程的余數,把 $lcm(m_1,m_2)$ 當做新的方程的模數。

合並完成:

$$x \equiv x' \quad (mod\;lcm(m_1,m_2))$$

poj 2891代碼


#include<cstdio>
#define
int long long const int N=1e5+5; int n,a[N],r[N];int exgcd(int a,int b,int &x,int &y){ if(!b){x=1,y=0;return a;} int d=exgcd(b,a%b,y,x);y-=a/b*x;return d; } int CRT(){ int A=a[1],R=r[1]; for(int i=2;i<=n;i++){ int x,y,d=exgcd(A,a[i],x,y),z=A/d*a[i]; if((r[i]-R)%d) return -1; x*=(r[i]-R)/d;x=(x%(a[i]/d)+a[i]/d)%(a[i]/d);//求出通解之后算出最小整數解 (R+=x*A)%=z;A=z; } return R; } signed main(){ while(~scanf("%lld",&n)){ for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i],&r[i]); printf("%lld\n",CRT()); } return 0; }

 

 

 

 

1.3 高斯消元


  假設有一個線性方程組是長這樣的:

$$\begin{Bmatrix}3x&+&2y&+&z&=&10\\5x&+&y&+&6z&=&25\\2x&+&3y&+&4z&=&20\end{Bmatrix}$$

  $emmm$ 這就是一個很簡單的三元一次方程,讓我們想想常規方法該怎么做(先不談 $ code$ )

  初中老師說過:我們可以加減消元或者代入消元,但是我們需要在程序里實現的時候,需要一種有規律可循的算法。所以我們選擇加減消元,但用代入消元回帶。

  整體思路就是我們可以先在某一個式子里,用這個式子的 $x$ 消去其他式子里的 $x$ ,然后在剩下的兩個式子里再選擇一個式子里的 $y$ ,用這個 $y$ 消去最后剩下的式子里的$ y$ 。那么現在最后一個方程里就只有一個未知數 $z$ 了。倘若 $z$ 的系數是 $1$ ,那么我們就可以直接得出答案來了(別覺得這句話是廢話)。

  比如剛才這個方程,我們用第二個式子去消1、3式里的 $x$ :

$$\begin{Bmatrix} 0 \times x&+& \frac{7}{5}y&+&(-\frac{13}{5})&=&-5\\5x&+&y&+&6z&=&25\\0\times x&+&\frac{13}{5}&+&\frac{8}{5}z&=&10\end{Bmatrix}$$

  整理之后再用第三個式子里的 $y$ 消去第一個式子里的 $y$ (注意,由於第二個式子作為消元用式,所以接下來的運算不再考慮二式):

$$\begin{Bmatrix}0\times y&+&(-\frac{225}{65}z)&=&-\frac{135}{13}\\ \frac{13}{5}y&+&\frac{8}{5}z&=&10\end{Bmatrix}$$

  那么我們發現在 $1$ 式中只剩下一個未知數了,那么就可解得: $$z=3$$
  帶回三式里解出
$$y=2$$
  再將 $x$、$y$ 帶回最早被消掉的二式里,解得
$$x=1$$
  好像這個方法再數學邏輯上講是特別麻煩的,但是卻是一個通用性強的方法

  那么放在程序實現上來講,我們可以先用一個 $n\times (n+1)$ 的矩陣來記錄每一個式子的系數以及結果。譬如上式就可以用矩陣表示成這樣:
$$\begin{Bmatrix} 3&2&1&\mid&10\\5&1&6&\mid&25\\2&3&4&\mid&20\end{Bmatrix}$$
  左邊記錄系數,右邊記錄每個式子的結果。
 
  那么首先我們需要用第一列中(所有的 $x$ 中)系數最大的來消其他兩個式子。而這里為了方便起見,我們將這個選中的系數置為 $1$ ,方便上例中地不斷帶回原式的操作(這樣在回帶的時候就可以不考慮原本的系數了)。
 
代碼:

 

 

#include<cmath> #include<cstdio> #include<algorithm>
#define db double
#define eps 1e-8

int n; db ans[105]; db a[105][105]; signed main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ for(int j=1;j<=n+1;j++) scanf("%lf",&a[i][j]); } for(int i=1;i<=n;i++){ int r=0; for(int j=i;j<=n;j++){ if(fabs(a[j][i]-a[r][i])) r=j; } if(!r){ printf("No Solution"); return 0; } std::swap(a[i],a[r]); for(int j=i+1;j<=n;j++){ db tmp=a[j][i]/a[i][i]; for(int k=i;k<=n+1;k++) a[j][k]-=tmp*a[i][k]; } } ans[n]=a[n][n+1]/a[n][n]; for(int i=n-1;i;i--){ for(int j=n;j>i;j--) a[i][n+1]-=ans[j]*a[i][j]; ans[i]=a[i][n+1]/a[i][i]; } for(int i=1;i<=n;i++) printf("%.2lf\n",ans[i]); return 0; }

 

     upd on 2018.7.8

    輾轉相減高斯消元

      剛做這題學到了個新姿勢。

      高斯消元的過程中如果要求模一個非質數該怎么做呢?

      考慮更相減損術。對於元素 a,b,想把 a 變成 0,那么就要不斷地令 a=a%b,swap(a,b),直到有一個數變成 0 為止。

luogu4111 code

#include<cstdio>
#include<cctype>
#include<cstring>
#include<iostream>
const int mod=1e9;
#define int long long
#define min(A,B) ((A)<(B)?(A):(B))
#define max(A,B) ((A)>(B)?(A):(B))
#define swap(A,B) ((A)^=(B)^=(A)^=(B))

int n,m,cnt;
int mp[12][12];
int a[105][105],b[105][105];
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};

int getint(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch)) f|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x;
}

signed main(){
    n=getint(),m=getint();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            char ch;std::cin>>ch;
            if(ch=='*') continue;
            mp[i][j]=++cnt;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!mp[i][j]) continue;
            for(int k=0;k<4;k++){
                int nx=i+dx[k];
                int ny=j+dy[k];
                if(nx<1 or nx>n or ny<1 or ny>m) continue;
                if(!mp[nx][ny]) continue;
                a[mp[i][j]][mp[i][j]]++;
                a[mp[i][j]][mp[nx][ny]]--;
            }
        }
    }
    int ans=1;
    for(int i=1;i<cnt;i++){
        for(int j=i+1;j<cnt;j++){
            while(a[j][i]){
                int t=a[i][i]/a[j][i];
                for(int k=i;k<cnt;k++){
                    a[i][k]-=t*a[j][k];
                    a[i][k]=(a[i][k]+mod)%mod;
                    swap(a[j][k],a[i][k]);
                }
                ans*=-1;
            }
        }
        ans=ans*a[i][i]%mod;
        ans=(ans+mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

 

 

    1.4 Baby Step,Giant Step

      1.4.0 BSGS

       問題:給定整數 $a,b,p$,其中 $gcd(a,p)=1$,求一個最小的非負整數 $x$,使 $a^x\equiv b\;(mod\;p)$

     由歐拉定理易得 $x\in [0,\phi(p)-1]$。設 $x=x_1+x_2$,那么 $a^{x_1}\equiv b\times (a^{x_2})^{-1}$。

     建立一個 $Hash$ 表,存下 $a^q=n$ 的情況下 $n$ 對應的最小正整數 $q$。

     我們枚舉 $x_2$,同時算出 $m=b\times (a^{x_2})^{-1}$,然后查表,看是否有整數 $p$ 對應 $a^p=m$,如果有,那么可以用 $p+x_2$ 更新答案。

     據均值不等式,枚舉 $x_2=k\times \sqrt{\phi(p)}$ 時最優。

     upd on 2018.7.4  改了一個小錯誤感謝sx小伙伴提醒

    1.4.1 擴展BSGS

     要求 $gcd(a,p)=1$ 太特殊了,對於更一般的情況,如果 $a,p$ 不互質,那該怎么辦呢?

     由上面互質的做法得到啟發,我們是不是可以把它變成互質的,然后繼續做呢?

     具體做法如下:

    1.當 $gcd(a,p)\ne 1$ 時, $a,b,p$ 同時除以 $gcd(a,p)$,直到  $b\nmid gcd(a,p)$ 或者 $gcd(a,p)=1$,並記錄 $num$ 為除了多少次。

    2.如果 $b\nmid gcd(a,p)$ 並且 $b!=1$ ,那么同余方程顯然無解。

    3.將式子轉化為 $a_0^{x-num}\equiv b_0\times (a_1)^{-1}\;(mod\;p_0)$

    4.觀察到這已經是 $BSGS$ 可以解決的范疇了。但是還有一點要注意的是直接 $BSGS$ 的話可能會漏掉 $x=[0,1...num-1]$ 的解,所以這里暴力跑一下快速冪  判斷即可。

       SPOJ3105 Code

       

#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#define int long long
#define min(A,B) ((A)<(B)?(A):(B))

int a,b,p;
std::map<int,int> mp;

int gcd(int x,int y){
    if(!y) return x;
    return gcd(y,x%y);
}

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

int inv(int a,int b){
    int x,y;
    exgcd(a,b,x,y);
    return (x%b+b)%b;
}

int ksm(int a,int b,int c){
    int ans=1;
    while(b){
        if(b&1) ans=ans*a%c;
        a=a*a%c;
        b>>=1;
    }
    return ans;
}

int phi(int n){
    int ans=n,sq=sqrt(n);
    for(int i=2;i<=sq;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;
}

int solve(){
    int aa=a,bb=b,pp=p,a2=1,t;
    int num=0;
    while((t=gcd(a,p))!=1 and b%t==0){
        a2*=a/t; b/=t; p/=t; 
        num++; a2%=p;
    }
    b=b*inv(a2,p)%p;
    if(b==1)
        return num;
    int d=gcd(a,p);
    if(b%d)
        return -1;
    int now=phi(p);
    int block=sqrt(now);
    int k=ksm(a,block,p);
    int last=1;
    for(int i=0;i<=num;last=last*a%pp,i++){
        if(last==bb)
            return i;
    }
    last=1;
    for(int i=0;i<=block;last=last*a%p,i++){
        int q=last;
        if(!mp.count(q))
            mp[q]=i;
    }
    last=1;
    for(int i=0;i<=block;i++,last=last*k%p){
        int q=b*inv(last,p)%p;
        if(mp.count(q))
            return i*block+mp[q]+num;
    }
    return -1;
}

signed main(){
    while(1){
        scanf("%lld%lld%lld",&a,&p,&b);
        if(!a)
            return 0;
        b%=p;
        int k=solve();
        printf(k==-1?"No Solution\n":"%lld\n",k);
        mp.clear();
    }
}

 

 

 

2 組合數學

  2.1 Catalan數

     2.1.0 帶限制條件的路徑總數

 首先我們來看一個問題:

 在一個平面直角坐標系中,只能往右或往上走一個單位長度,問有多少種不同的路徑可以從左下角 (1, 1) 走到右上角 (n, n),並且要求路徑不能經過直線 y = x 上方的點,下圖中的路徑都是合法的(圖片來源 Wikipedia)

 如果沒有限制條件,那么從左下角走到右上角一共有 2n 步,有 n 步是往右,另外 n 步是往上,那么路徑方案數就是 2n 步中選擇 n 步往右,一共有 {2n} \choose {n} (即 C_{2n}^n)種方案

 那么我們考慮一下這里面有多少種方案是不合法的

 首先對於每一種不合法的方案,它的路徑一定與 y = x + 1 有交。我們找到它與 y = x + 1 的第一個交點,然后將這個點后面部分的路徑關於 y = x + 1 做一個對稱。由於原來路徑到達 (n, n),新的對稱之后的路徑就會到達 (n - 1, n + 1)。這樣我們把每一種不合法方案都對應到了一條從 (1, 1) 到 (n - 1, n + 1) 的路徑,現在再來看是否每一條這樣的路徑都能對應到一種不合法方案,如果是,那么這就建立了一個一一映射的關系,也就是它們的方案總數相同。這是肯定的,因為每一條這樣的路徑必定與 y = x + 1 有交,那么對稱回去,就得到一條不合法方案

 由於從 (1, 1) 到 (n - 1, n + 1) 的路徑有 {n - 1 + n + 1} \choose {n - 1} 條,那么合法的  方案就是

 C_n = {{2n} \choose {n}} - {{2n} \choose {n - 1}} = \frac{1}{n + 1} {{2n} \choose {n}}

 我們把這個方案數記為 C_n,這就是著名的 Catalan 數

 我們來看看它的前幾項(n 從 0 開始)

 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190

 

 2.1.1 括號序列計數

 再來看一個問題:有多少種不同的長度為 n 的括號序列?

 首先一個括號序列是指 (), ()(), (())() 這樣的由括號組成的序列,並且沒有左右括號無法匹配的情況

 我們可以將長度為 2n 的括號序列映射成剛剛所說的路徑:首先對於左括號,那么就向右走一個單位長度,對於右括號,那么就向上走一個單位長度,由於括號序列合法,那么每次向右走的次數不會少於向上的次數,也就是這條路徑不會在 y = x 之上。再考慮每一條這樣的路徑,也能夠對應到一種合法的括號序列,因此,長度為 2n 的括號序列的方案數就是 C_n

 2.1.2 出棧順序

 現在來考慮你有 n 個元素(元素之間是沒有區別的)和一個棧,每次可以將一個元素入棧,或者將棧頂元素彈出,問有多少種可能的操作序列,這可以將問題對應成括號序列,入棧為左括號,出棧為右括號,因此方案數也是 C_n

   2.1.3 排隊問題

 現在有 2n 個人,他們身高互不相同,他們要成兩排,每一排有 n 個人,並且滿足每一排必須是從矮到高,且后一排的人要比前一排對應的人要高,問有多少種排法

 我們考慮先把這些人從矮到高排成一排,那么現在來分配哪個人在前,哪個人在后,例如有 6個人,身高是 1, 2, 3, 4, 5, 6

 那么我們用 1 表示這個人應該在后排,0 表示這個人應該在前排,例如說 100110 表示兩排分別是 2, 3, 6 和 1, 4, 5 這是不合法的

 那么合法方案應該是怎么樣的呢?后排要比前排對應的人高,也就是說 0 的出現次數在每一個地方都不應該小於 1,這恰恰又是一個括號序列,因此,方案仍然是 Catalan 數

 2.1.4 二叉樹計數

 現在你需要統計有多少種不同的 n 個結點的二叉樹

 

 圖上的是 3 個結點的二叉樹,一共有 5 種方案

 朴素的想法是由於二叉樹是遞歸定義的,可以考慮使用遞推方法

 我們可以用 f_n 表示有 n 個結點的二叉樹的方案數(空樹算一種,即 $f_0=1$ ),那么枚舉子樹大小可以得到方程

 f_n = \sum_{i = 0}^{n - 1} f_if_{n - i - 1}

 如果直接計算,你需要 \mathcal O(n^2) 的時間

 現在我們換一個角度來想,對這棵二叉樹進行遍歷,並且考慮一個括號序列,當第一次遇到這個結點的時候,在括號序列末尾添加一個左括號,在從左子樹回到這個結點的時候,在括號序列中添加一個右括號,這樣,將每一種不同的二叉樹都對應到了一種不同的括號序列,同樣對於每一種不同的括號序列都可以找到對應的一種不同的二叉樹,因此,有 n 個結點的二叉樹的數量也是 C_n

2.2 $Lucas$ 定理 (摘自Navi-Awson)

  2.2.0 $Lucas$

    $C_n^m\equiv C^{\lfloor{\frac{m}{p}}\rfloor}_{\lfloor{\frac{n}{p}}\rfloor}\times C_{n\;mod\;p}^{m\;mod\;p}\quad (mod\;p)$,$p$ 是質數。

  證明:需要用到多項式同余的那套理論,然而我並不會。

  2.2.1 擴展 $Lucas$

    一般的 $Lucas$ 是在模數 $P$ 是質數的條件下適用的。我們來考慮 $P$ 不是質數的條件。

    我們對 $P$ 進行唯一分解,記 $P=p_1^{k_1}p_2^{k_2}...p_q^{k_q}$,由於形同 $p_i^{k_i}$ 的部分是互質的,顯然我們可以用 $CRT$ 合並。

    列出方程組:

$$ans\equiv c_1\quad (mod\;p_1^{k_1})$$

$$ans\equiv c_2\quad (mod\;p_2^{k_2})$$

$$...$$

$$ans\equiv c_q\quad (mod\;p_q^{k_q})$$

    對於每個 $c_i$,表示 $C_n^m$ 在 $mod\;p_i^{k_i}$ 下的結果。由解的唯一性,我們可以證明這個 $ans$ 就是我們要求的。

    根據 $C_n^m=\frac{n!}{m!(n-m)!}$ 我們只要求出 $n!\;mod\;p_i^{k_i},m!\;mod\;p_i^{k_i},(n-m)!\;mod\;p_i^{k_i}$,再用逆元的那套理論就可以求 $c_i$ 了。

    考慮如何求 $n!\;mod\;p_i^{k_i}$ 。容易發現 $n!= (\Pi_{j=1,p_i\nmid j}^{n}j )\times  (p_i^{\lfloor{\frac{n}{p_i}}\rfloor} )\times  (\lfloor{\frac{n}{p_i}}\rfloor !)$

    上述式子分為三個部分,第一個部分顯然在模 $p_i^{k_i}$ 下,是以 $p_i^{k_i}$ 為周期的。可以周期內找循環節算,周期外的暴力算;第二部分可以直接算,第三部分可以遞歸求解。

    另外注意的是求組合逆元的是,存在階乘中的某一個數可能還有 $p_i$ 這個質因子,不能直接算。直接把 $p_i$ 全部提出來,最后求完逆元再補回去。求 $n!$ 內質因子 $p$ 的個數可以用 $\sum _{i=1}^{\infty}\lfloor{\frac{n}{p^i}}\rfloor$ 來求。

    代碼:

int ksm(int a,int b,int x){ int ans=1;a%=x; while(b){ if(b&1) ans=(ans*a)%x; a=(a*a)%x; b>>=1; } return ans; } int exgcd(int a,int b,int &x,int &y){ if(!b){ x=1,y=0; return a; } int d=exgcd(b,a%b,x,y); int t=x; x=y; y=t-a/b*y; return d; } int inv(int a,int b){ int x,y; exgcd(a,b,x,y); x=(x%b+b)%b; return x; } int solve(int a,int cur){ if(!a) return 1; int ans=1; for(int i=2;i<c[cur];i++){ if(i%p[cur]) ans=(ans*i)%c[cur]; } ans=ksm(ans,a/c[cur],c[cur]); for(int i=2;i<=a%c[cur];i++){ if(i%p[cur]) ans=(ans*i)%c[cur]; } return ans*solve(a/p[cur],cur)%c[cur]; } int C(int cur,int a,int b){ if(a<b) return 0; int x=solve(a,cur); int y=solve(b,cur); int z=solve(a-b,cur); int k=0; for(int i=a;i;i/=p[cur]) k+=i/p[cur]; for(int i=b;i;i/=p[cur]) k-=i/p[cur]; for(int i=a-b;i;i/=p[cur]) k-=i/p[cur]; int ans=(x*inv(y,c[cur])*inv(z,c[cur])*ksm(p[cur],k,c[cur]))%c[cur]; return ans; } int CRT(){ int ans=0; for(int i=1;i<=cnt;i++) ans=(ans+inv(mod/b[i],b[i])*(mod/b[i])*a[i])%mod; return ans; }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM