组合数定义
C a b = a ! b ! ( a − b ) ! C^{b}_{a} = \frac{a!}{b!(a-b)!} Cab=b!(a−b)!a!
带模数
数据范围(a 、b较小)
1 ≤ n ≤ 10000 1≤n≤10000 1≤n≤10000,
1 ≤ b ≤ a ≤ 2000 1≤b≤a≤2000 1≤b≤a≤2000
m o d = 1 e 9 + 7 mod = 1e9+7 mod=1e9+7
C i j = = C i − 1 j + C i − 1 j − 1 C^{j}_{i} == C^{j}_{i-1} + C^{j-1}_{i-1} Cij==Ci−1j+Ci−1j−1
const int N = 2005;
int ans[N][N];
void init() {
// 递推预处理
for (int i = 0;i < N;++i) {
for (int j = 0;j <= i;++j) {
if (!j)ans[i][j] = 1;
else ans[i][j] = (ans[i - 1][j] + ans[i - 1][j - 1]) % mod;
}
}
}
int main() {
init();
int n;cin >> n;
while (n--) {
int a, b;cin >> a >> b;
printf("%d\n", ans[a][b]);
}
return 0;
}
数据范围(a、b较大)
1 ≤ n ≤ 10000 , 1≤n≤10000, 1≤n≤10000,
1 ≤ b ≤ a ≤ 1 0 5 1≤b≤a≤10^{5} 1≤b≤a≤105
m o d = 1 e 9 + 7 mod = 1e9 + 7 mod=1e9+7
const int N = 100010;
int fact[N], infact[N];
int qmi(int a, int b) {
int res = 1;
while (b) {
if (b & 1)res = (LL)res * a % mod;
a = (LL)a * a % mod;
b >>= 1;
}
return res;
}
int main() {
fact[0] = infact[0] = 1;
for (int i = 1;i < N;++i) {
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2) % mod; //求逆元
}
int n;cin >> n;
while (n--) {
int a, b;
cin >> a >> b;
printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);
}
return 0;
}
数据范围(a、b非常大)
1 ≤ n ≤ 20 1≤n≤20 1≤n≤20
1 ≤ b ≤ a ≤ 1 0 18 1≤b≤a≤10^{18} 1≤b≤a≤1018
1 ≤ p ≤ 1 0 5 1≤p≤10^{5} 1≤p≤105
卢卡斯定理
C a b ≡ C a m o d p b m o d p ⋅ C a / p b / p ( m o d p ) C^{b}_{a} \equiv C_{a \ mod \ p}^{b \ mod \ p} · C_{a/p}^{b/p}(mod \ p) Cab≡Ca mod pb mod p⋅Ca/pb/p(mod p)
const int N = 100010;
int p;
int qmi(int a, int b) {
int res = 1;
while (b) {
if (b & 1)res = (LL)res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
int C(int a, int b) {
if (b > a)return 0;
int res = 1;
for (int i = 1, j = a;i <= b;++i, --j) {
res = (LL)res * j % p;
res = (LL)res * qmi(i, p - 2) % p;
}
return res;
}
int lucas(LL a, LL b) {
if (a < p && b < p)return C(a, b);
return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}
int main() {
int n;cin >> n;
while (n--) {
LL a, b;
cin >> a >> b >> p;
cout << lucas(a, b) << endl;
}
return 0;
}
无模数
数据范围
1 ≤ b ≤ a ≤ 5000 1≤b≤a≤5000 1≤b≤a≤5000
根据 C a b = a ! b ! ( a − b ) ! C^{b}_{a} = \frac{a!}{b!(a-b)!} Cab=b!(a−b)!a!
等式右边分子分母分解质因数后约掉 再用高精度乘法
——------------------------------------------------------------------------------------------------------------------------------------
const int N = 100010;
bool st[N];
int primes[N], cnt;
int sum[N];
//欧拉筛
void get_primes(int n) {
for (int i = 2;i <= n;++i) {
if (!st[i]) {
primes[cnt++] = i;
}
for (int j = 0;primes[j] <= n / i;++j) {
st[primes[j] * i] = true;
if (i % primes[j] == 0)break;
}
}
}
//求n的阶乘中 质因子p的次数
int get(int n, int p) {
int res = 0;
while (n) {
res += n / p;
n /= p;
}
return res;
}
//高精度乘法
vector<int>mul(vector<int>a, int b) {
vector<int >c;
int t = 0;
for(int i = 0;i < a.size() || t;++i){
if(i < a.size())t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main() {
int a, b;cin >> a >> b;
get_primes(a);
for (int i = 0;i < cnt;i++) {
int p = primes[i];
sum[i] = get(a,p) - get(b,p) - get(a - b,p);//最终结果中质因子p的次数
}
vector<int>ans;
ans.push_back(1);
//因为数据太大可能会溢出LL 所以不用快速幂 用高精度乘法
for (int i = 0;i < cnt;++i) {
for (int j = 0; j < sum[i];++j) {
ans = mul(ans, primes[i]);//将最终化简结果的质因数分解形式变成原来的数据
}
}
for (int i = ans.size() - 1;i >= 0;--i)printf("%d", ans[i]);
cout << endl;
return 0;
}
卡特兰数
C 2 n n − C 2 n n − 1 C_{2n}^{n} - C_{2n}^{n-1} C2nn−C2nn−1 = C 2 n n n − 1 = \frac{C_{2n}^{n}}{n-1} =n−1C2nn
从 ( 0 , 0 ) (0,0) (0,0)到 ( n , n ) (n,n) (n,n)所有不经过对角线的路径方案数
所有可能的路径数 C 2 n n C_{2n}^{n} C2nn
不满足条件的路径为经过红线的路径 C 2 n n − 1 C_{2n}^{n-1} C2nn−1
所以满足条件的方案数为 C 2 n n − C 2 n n − 1 C_{2n}^{n} - C_{2n}^{n-1} C2nn−C2nn−1 = C 2 n n n − 1 = \frac{C_{2n}^{n}}{n-1} =n−1C2nn 即为卡特兰数