【NC20277】字符串

题目

字符串

卢卡斯定理,费马小定理,组合数,卡特兰数

思路

卡特兰数

关于卡特兰数有很多很多有意思的数学场景,比如一个 n ∗ n n*n nn 的方格图,让你从左下角走到右上角,每次只能向右或者上走,并且只能在方格图的下三角中行走,有多少种方法?

  • 假设向右走为 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 nm 而不仅仅是等于。不过重要的是能以上面走格子的思想来解决本题。

给你一个 n ∗ m ( n ≥ m ) n*m(n\ge m) nm(nm) 的方格图,让你从左下角走到右上角,每次只能向右或者上走,有多少种方法?

显然,利用组合数学的知识可以轻易的得到一个答案: 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,m1)

为什么能得到上面的答案呢?

具体的证明牵扯了太多的数学知识,这里不给出。况且已经有前人的题解给出了思路,这里给出链接:

  1. 坐标系转换
  2. 组合的思想

不过在笔者看来,大多数情况下都要大胆猜测,既然已经知道这道题和卡特兰数有点什么关联,那么索性直接让它们关联起来:

卡特兰数: 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,n1)=C(n+n,n)C(n+n,n1)
本题类比: 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,m1) 或者 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,n1),这两个里面很大概率有一个是对的,挨个尝试提交即可。

不过前提得知道卡特兰数是 C ( 2 n , n ) − C ( 2 n , n − 1 ) C(2n,n)-C(2n,n-1) C(2n,n)C(2n,n1)

那么问题就转化为了求组合数,这里用到卢卡斯定理:

简单来说,卢卡斯定理是组合数学中的一个重要定理,用于计算组合数在模意义下的值。它的一个重要应用是计算组合数取模,可以避免因为组合数太大而导致的数值溢出。

不过需要注意的是,如果 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木又可可

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值