1. 程式人生 > >Catalan Number & Lucas定理 & 中國剩餘定理(CRT)

Catalan Number & Lucas定理 & 中國剩餘定理(CRT)

又雙叒叕來水數論了 今天來學習$Lucas \:\ \& \:\ Catalan Number$ 兩者有著密切的聯絡(當然還有CRT),所以放在一起學習一下 # Catalan Number ## 定義 卡特蘭數(Catalan Number)又稱卡塔蘭數,是組合數學中一個經常出現在各種計數問題中的數列 $1,2,5,14,42,132,429,1430,4862,16796,58786,208010,742900$ 啊這 其實跟$Lucas$沒有什麼太大的聯絡 只有其中一個求$Catalan Number$的公式可能會用到$Lucas$定理 ## 性質 上面提到說**其中一個**公式,也就是說求$Catalan Number$有很多方法 * 遞迴公式1 $$ f(n) = \sum_{i=0}^{n-1}f(i)*f(n-i-1)$$ * 遞迴公式2 $$ f(n)=\frac {f(n-1)*(4*n-2)} {n+1} $$ * 組合公式1(就是這個的分子可能會用到$Lucas$忽然找不到手頭做過的那道題了)當然這個式子也是它的**通項公式** $$f(n)= \frac {C_{2n}^n} {n+1}$$ * 組合公式2(同上) $$f(n) = C_{2n}^n - C_{2n}^{n-1}$$ ## 應用背景 一般用來解決一些現實問題,很複雜的題目被看破之後直接用公式算出來即可 舉個例子,**合法的入棧出棧序列有多少種就是卡特蘭數**。我們可以把0看成入棧操作,1看成出棧操作,即0的累計個數不小於1的排列有多少種。 還有很多其他的問題都是卡特蘭數,如括號匹配,二叉樹的個數,有序樹的個數,多邊形分成三角形的個數等。 * n+1 個葉子節點的二叉樹的數量 ![](https://upload.wikimedia.org/wikipedia/commons/0/01/Catalan_number_binary_tree_example.png) * n*n的方格地圖中,從一個角到另外一個角,不跨越對角線的路徑數 ![](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/Catalan_number_4x4_grid_example.svg/450px-Catalan_number_4x4_grid_example.svg.png) * n+2條邊的多邊形,能被分割成三角形的方案數 ![](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Catalan-Hexagons-example.svg/400px-Catalan-Hexagons-example.svg.png) ## 實現過程 來看一道[例題](https://www.luogu.com.cn/problem/P1722)做入門吧 ### 題目描述 眾所周知,在中國古代算籌中,紅為正,黑為負…… 給定一個$1*(2n)$的矩陣,現讓你自由地放入紅色算籌和黑色算籌,使矩陣平衡[即對於所有的$i(1<=i<=2n)$,使第$1-i$格中紅色算籌個數大於等於黑色算籌] 問有多少種方案滿足矩陣平衡。 ### 輸入格式 正整數 n ### 輸出格式 方案數t對100取模 ### 輸入輸出樣例 #### Input ```cpp 2 ``` #### Output ``` 2 ``` ### 樣例解釋 紅 黑 紅 黑 紅 紅 黑 黑 ### solution * 將紅看做入棧操作,黑看為出棧操作,問題即為求不同的合法的入棧出棧序個數 * 我們可以考慮用遞推式(1)求卡特蘭數,取模更容易 ### code ```cpp #include #include #include #include using namespace std; int main(){ long long ctl[110],i,j,k,n; memset(ctl,0,sizeof(ctl)); ctl[0]=ctl[1]=1; ctl[2]=2; scanf("%lld",&n); for(i=3;i<=n;++i) for(j=0;jmod$,那麼取$mod$之後整個式子的值就會變成0,而$Lucas$的出現,就是為了解決這一問題 ## 引入 針對上述第二種情況,我們先來看一道例題 ### 旗木雙翼 #### 題目描述 菲菲和牛牛在一塊n行m列的棋盤上下棋,菲菲執黑棋先手,牛牛執白棋後手。 棋局開始時,棋盤上沒有任何棋子,兩人輪流在格子上落子,直到填滿棋盤時結束。落子的規則是:一個格子可以落子當且僅當這個格子內沒有棋子且這個格子的左側及上方的所有格子內都有棋子。 _Itachi聽說有不少學弟在省選現場AC了D1T1,解決了菲菲和牛牛的問題,但是_Itachi聽說有的人認為複雜度玄學,_Itachi並不想難為學弟學妹,他想為大家節約時間做剩下的題,所以將簡化版的D1T1帶給大家。 _Itachi也在一塊n行m列的棋盤上下棋,不幸的是_Itachi只有黑棋,不過幸好只有他一個人玩。現在,_Itachi想知道,一共有多少種可能的棋局(不考慮落子順序,只考慮棋子位置)。 _Itachi也不會為難學弟學妹們去寫高精度,所以只需要告訴_Itachi答案mod 998244353(一個質數)的結果。 #### 輸入格式 第一行包括兩個整數n,m表示棋盤為n行m列。 #### 輸出格式 一個整數表示可能的棋局種數。 #### 樣例輸入 10 10 #### 樣例輸出 184756 #### 資料範圍與提示 對於 $20\%$的資料$n,m \leq10 $ 對於 $30\%$的資料$n,m\leq20 $ 另有 $20\%$的資料$n\leq5 $ 另有 $20\%$的資料$m\leq5 $ 對於$100\%$的資料$n,m\leq100000 $ #### solution 可以看到題目當中直接給出了$mod$為一個大質數 而且$m+n$最大也才二十萬,遠遠小於$mod$的數值 所以不會出現取完$mod$為$0$的情況,所以運用逆元求解即可 $$C_{n+m}^m =\frac {(m+n)!} {n!*m!} \% mod$$ #### Code ```cpp #include #include #include #include #define int long long using namespace std; inline int read(){ int x = 0, w = 1; char ch = getchar(); for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0'; return x * w; } const int mod = 998244353; inline int power(int a, int b){ int ans = 1; while(b){ if(b & 1) ans = (ans % mod) * (a % mod) % mod; a = (a % mod) * (a % mod) % mod; b >>= 1; } ans %= mod; return ans; } signed main(){ int n = read(), m = read(); int tmp = 1; for(int i = m + n; i >= 1; i--){ tmp *= i; tmp %= mod; } int n1 = 1; int m1 = 1; for(int i = 1; i <= n; i++){ n1 *= i; n1 %= mod; } for(int i = 1; i <= m; i++){ m1 *= i; m1 %= mod; } int ans = (tmp % mod) * power(n1 ,mod - 2) % mod * power(m1, mod - 2) % mod; ans %= mod; cout << ans << endl; return 0; } ``` 如果$mod$很小 上述演算法顯然在求階乘的時候會變成$0$ 這就需要$Lucas$定理了 ## 性質 ### 性質1 $$Lucas(n,m,p)=cm(n\%p,m\%p)*Lucas(\frac n p,\frac m p,p)$$ $$Lucas(x,0,p) =1$$ 其中 $$cm(a,b)=a!*(b!*(a-b)!)^{p-2} (mod \:\ p)$$ $ \:\ \:\ \:\ \:\ \:\ \:\ \:\ \:\ \:\ $ $(a-b)!^{p-2}$為$a-b$的逆元,所以可以除一下把式子變成 $$ (a!/(a-b)!)*(b!)^{p-2}(mod \:\ p)$$ ### 性質2 $$C_n^mmod p\equiv C_{n\mod p}^{m\mod p}*C_{\lfloor n/p\rfloor}^{\lfloor m/p\rfloor}mod p $$ 就是一個$a,b$可以拆成$P$進位制下的乘積 ## 證明 目標方程: $$\dbinom n m\equiv\dbinom {n\%p} {m\%p} \dbinom {n/p}{m/p}(mod p) $$ 顯然右側可以遞迴處理,我們只需要推左邊就好 證明過程: 設 $$ n=sp+q,m=tp+r,(q,r #include #include #include #define int long long using namespace std; inline int read(){ int x = 0, w = 1; char ch = getchar(); for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0'; return x * w; } int n, m, p; inline int power(int a, int b){ int ans = 1; while(b){ if(b & 1) ans = (ans % p) * (a % p) % p; a = (a % p) * (a % p) % p; b >>= 1; } ans %= p; return ans; } inline int getc(int n, int m){ if(n < m) return 0; if(m > n - m) m = n - m; int s1 = 1, s2 = 1; for(int i = 0; i < m; i++){ s1 = s1 * (n - i) % p;//(n-m)!/n! s2 = s2 * (i + 1) % p;//m! } return s1 * power(s2, p - 2) % p; } inline int lucas(int n, int m){ if(m == 0) return 1; return getc(n % p, m % p) * lucas(n / p, m / p) % p; } signed main(){ int t = read(); while(t--){ n = read(), m = read(), p = read(); cout << lucas(n + m, m) << endl; } return 0; } ```