题目
卢卡斯定理,费马小定理,组合数,卡特兰数
思路
卡特兰数
关于卡特兰数有很多很多有意思的数学场景,比如一个 n ∗ n n*n n∗n 的方格图,让你从左下角走到右上角,每次只能向右或者上走,并且只能在方格图的下三角中行走,有多少种方法?
- 假设向右走为 1 1 1,向上走为 0 0 0,那么就变成了给你 n n n 个 0 0 0 和 1 1 1,计算能组成的对任意前 k k k 个字符,其中 1 1 1 的个数不少于 0 0 0 的字符串个数。
是不是和本题极为相似呢?
但是本题要求 n ≥ m n\ge m n≥m 而不仅仅是等于。不过重要的是能以上面走格子的思想来解决本题。
给你一个 n ∗ m ( n ≥ m ) n*m(n\ge m) n∗m(n≥m) 的方格图,让你从左下角走到右上角,每次只能向右或者上走,有多少种方法?
显然,利用组合数学的知识可以轻易的得到一个答案: C ( n + m , m ) C(n+m,m) C(n+m,m)
当没有任何限制的时候,上面的答案就是正确的,或者说上面的答案是总的方案数,我们要根据限制条件来减去总方案中不符合的方法数。
不符合题意的方案数: C ( n + m , m − 1 ) C(n+m,m-1) C(n+m,m−1)
为什么能得到上面的答案呢?
具体的证明牵扯了太多的数学知识,这里不给出。况且已经有前人的题解给出了思路,这里给出链接:
不过在笔者看来,大多数情况下都要大胆猜测,既然已经知道这道题和卡特兰数有点什么关联,那么索性直接让它们关联起来:
卡特兰数: C ( 2 n , n ) − C ( 2 n , n − 1 ) = C ( n + n , n ) − C ( n + n , n − 1 ) C(2n,n)-C(2n,n-1)=C(n+n,n)-C(n+n,n-1) C(2n,n)−C(2n,n−1)=C(n+n,n)−C(n+n,n−1)
本题类比: C ( n + m , m ) − C ( n + m , m − 1 ) C(n+m,m)-C(n+m,m-1) C(n+m,m)−C(n+m,m−1) 或者 C ( n + m , m ) − C ( n + m , n − 1 ) C(n+m,m)-C(n+m,n-1) C(n+m,m)−C(n+m,n−1),这两个里面很大概率有一个是对的,挨个尝试提交即可。
不过前提得知道卡特兰数是 C ( 2 n , n ) − C ( 2 n , n − 1 ) C(2n,n)-C(2n,n-1) C(2n,n)−C(2n,n−1)
那么问题就转化为了求组合数,这里用到卢卡斯定理:
简单来说,卢卡斯定理是组合数学中的一个重要定理,用于计算组合数在模意义下的值。它的一个重要应用是计算组合数取模,可以避免因为组合数太大而导致的数值溢出。
不过需要注意的是,如果 M O D MOD MOD 不是质数,则卢卡斯定理不一定成立,但是这道题给的 M O D = 20100403 MOD=20100403 MOD=20100403 显然是个质数,所以可以使用卢卡斯定理求解。
代码
#include <stdio.h>
#define MOD 20100403
typedef long long LL;
// 快速幂
int quick_pow(int a, int b, int p) {
int res = 1;
while (b) {
if (b & 1) res = ((LL)res * a) % p;
a = ((LL)a * a) % p;
b >>= 1;
}
return res;
}
// 求 n 在模 p 意义下的逆元,要求 n 与 p 互质
int inv(int n, int p) { return quick_pow(n, p - 2, p); }
/**
* @brief 卢卡斯定理求组合数 C(n, m) 对 p 取模的结果
*
* @param n
* @param m
* @param p
* @return int
*/
int lucas(int n, int m, int p) {
if (!m) return 1;
int ni = n % p, mi = m % p;
if (ni < mi) return 0;
int res = 1;
for (int i = 0; i < mi; i++) {
res = (LL)res * (ni - i) % p * inv(i + 1, p) % p;
}
return res * lucas(n / p, m / p, p) % p;
}
int main(void) {
int n = 0, m = 0;
scanf("%d%d", &n, &m);
int ans = (lucas(n + m, m, MOD) - lucas(n + m, m - 1, MOD) + MOD) % MOD;
printf("%d\n", ans);
return 0;
}