使用免費GeoLite2-City.mmdb進行IP資訊解析和地理定位
又雙叒叕來水數論了
今天來學習\(Lucas \:\ \& \:\ Catalan Number\)
兩者有著密切的聯絡(當然還有CRT),所以放在一起學習一下
證明\(Lucas\)幾乎花了半天的時間,呼,終於懂了
希望讀者能有收穫
有疑問評論區見,蟹蟹
\(Lucas\)
定義\(\&\)性質
\(Lucas\)定理是用來求 $C_n^m mod :\ p \(的值。 其中\)n\(和\)m\(是非負整數,\)p\(是素數。 一般用於\)m,n\(很大而\)p\(很小,抑或是\)n,m\(不大但是大於\)p$的情況下來求結果。
用處\(\&\)背景
目前我們學過幾個用來求組合數的方法
- 最暴力的楊輝三角,用二維陣列解決,複雜度\(O(n^2)\) 效率低下而且由於陣列的原因只能處理很小的資料
- 將組合式\(C_{n+m}^n \% mod\)轉換為\(\frac {(m+n)!} {n! m!} \%mod\),由於除法不可以邊除邊取\(mod\)所以在處理分母的時候選用了費馬小定理,運用逆元的乘法解決。
看分子的話,\((m+n)!\)在遍歷的時候若有元素\(i>mod\),那麼取\(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
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#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<p) \]
則目標方程變成
\[\dbinom {sp+q} {tp+r} \equiv \dbinom {s} {t} \dbinom {q} {r} (mod p) \]
下面利用二項式定理&費馬小證明一個引理
\[(1+x)^n \equiv (1+x)^{ps} * (1+x)^q (mod \:\ p) \]
\[(1+x)^n\equiv[\sum_{t=0}^p \dbinom p i x^i]^s(mod \:\ p) \]
根據上面的那個性質
\[\equiv (1+x)^q*(1+x^p)^s(mod p) \]
\[\equiv \sum_{i=0}^s \binom s i x^{pt} * \sum_{j=0}^q \binom q j x^j(mod p) \]
則有
\[(1+x)^{sp+q} \equiv\sum_{i=0}^s \binom s i x^{pt} * \sum_{j=0}^q\binom q j x^j(mod p) \]
\[\sum_{k=0}^{sp+q}\binom {sp+q} {k} x^k\equiv\sum_{i=0}^s \binom s i x^{pt} * \sum_{j=0}^q\binom q j x^j(mod p) \]
左邊\(x^{tp+r}\)的係數為\(\binom {sp+q}{tp+r}\)
右邊\(x^{tp+r}\)的係數為\(\binom s t\binom q r\)
故係數相等,原命題得證
\[\dbinom {sp+q} {tp+r} \equiv \dbinom {s} {t} \dbinom {q} {r} (mod p) \]
即
\[\dbinom n m\equiv\dbinom {n\%p} {m\%p} \dbinom {n/p}{m/p}(mod p) \]
即
\[Lucas(n,m,p)=cm(n\%p,m\%p)*Lucas(\frac n p,\frac m p,p) \]
\[Lucas(x,0,p) =1 \]
(以上證明結束)
實現過程
- 目標運算結果\(C_n^m \:\ \%p\)
- 注意輸入的\(n,m\)中,可能會出現\(n\%p<m%p\),那麼直接輸出0即可。因為若\(n<m\),則\(C_n^m=0\)
- 在求逆元時,運用快速冪求其\(p-2\)次方
- 如果要大量運用\(Lucas\)定理,建議使用楊輝三角打組合數表或將階乘以及階乘逆元打表
Code
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#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;
}