1316 有趣的数列(卡特兰数--分解质因数 + 快速幂)

1. 问题描述:

我们称一个长度为 2n 的数列是有趣的,当且仅当该数列满足以下三个条件:

  • 它是从 1 到 2n 共 2n 个整数的一个排列 {ai};
  • 所有的奇数项满足 a1<a3<⋯<a2n−1 ,所有的偶数项满足 a2<a4<⋯<a2n;
  • 任意相邻的两项 a2i−1 与 a2i (1 ≤ i ≤ n) 满足奇数项小于偶数项,即:a2i−1<a2i。

任务是:对于给定的 n,请求出有多少个不同的长度为 2n 的有趣的数列。因为最后的答案可能很大,所以只要求输出答案 mod P 的值。

输入格式

只包含用空格隔开的两个整数 n 和 P。

输出格式

仅含一个整数,表示不同的长度为 2n 的有趣的数列个数 modP 的值。

数据范围

1 ≤ n ≤ 10 ^ 6,
2 ≤ P ≤ 10 ^ 9

输入样例:

3 10

输出样例:

5

样例解释
对应的 5 个有趣的数列分别为 {1,2,3,4,5,6},{1,2,3,5,4,6},{1,3,2,4,5,6},{1,3,2,5,4,6},{1,4,2,5,3,6}。
来源:https://www.acwing.com/problem/content/description/1318/

2. 思路分析:

分析题目可以知道第三项是5,所以应该想到卡特兰数,第三项为5的题目一般与卡特兰数都有关系,当我们知道卡特兰数之后那么就可以直接使用下面的公式求解了:

因为p不一定为质数所以不能够直接使用求解卡特兰数的第二个公式C2n n / (n + 1)通过求解逆元的方式求解最终的答案,这里可以使用分解质因数 + 快速幂的方式求解(类似于1315题),其实也好理解,对于Cnm我们可以使用阶乘相除的形式来计算出这个结果,先通过线性筛预处理出2 ~2n的所有质数,然后通过一个方法求解当前组合数公式中质因子为p的次数,然后使用快速幂求解出结果即可。题目的核心在于为什么是卡特兰数,一般来说卡特兰数的题目有两个特点:

  • 通过递推式:f(n) = f(1)f(n - 1) + f(2)f(n - 2) + ... ,对应求解满二叉树的个数
  • 挖掘一个比较重要的性质:任意前缀中某种东西  >= 另外一种东西,可以发现当我们从前往后选某个数是奇数项还是偶数项的时候需要满足奇数项的数目大于等于偶数项的数目(如果某个数是偶数项并且前面奇数项的数目小于偶数项的数目那么必然需要从大于当前位置的数字后面选这样前面的数字就大于后面的数字就矛盾了),这个就与卡特兰数的定义是很像的,我们可以将奇数项的数目看成是1,偶数项的数目看成是0,这样就就可以一一对应起来了,求解1的数目大于等于偶数项的数目的排列数目。

3. 代码如下:

from typing import List


class Solution:
    count = 0

    # 线性筛法求解质因数
    def init(self, n: int, primes: List[int], st: List[int]):
        for i in range(2, n):
            if st[i] == 0:
                primes[self.count] = i
                self.count += 1
            j = 0
            while i * primes[j] < n:
                st[primes[j] * i] = 1
                if i % primes[j] == 0: break
                j += 1
    
    # 求解阶乘中的质因子为p的次数
    def get(self, n: int, p: int):
        res = 0
        while n > 0:
            res += n // p
            n //= p
        return res
    
    # 快速幂
    def quickPower(self, a: int, b: int, p: int):
        res = 1
        while b > 0:
            if b & 1:
                res = res * a % p
            a = a * a % p
            b >>= 1
        return res
    
    # 使用分解质因数的方法求解组合数
    def C(self, a: int, b: int, p: int, primes: List[int]):
        res = 1
        for i in range(self.count):
            prime = primes[i]
            # 求解当前组合数公式中阶乘相除之后质因子为p最后剩余的次数s
            s = self.get(a, prime) - self.get(b, prime) - self.get(a - b, prime)
            res = res * self.quickPower(prime, s, p) % p
        return res

    def process(self):
        n, p = map(int, input().split())
        primes, st = [0] * (2 * n + 10), [0] * (2 * n + 10)
        self.count = 0
        self.init(2 * n + 1, primes, st)
        return (self.C(2 * n, n, p, primes) - self.C(2 * n, n - 1, p, primes) + p) % p


if __name__ == "__main__":
    print(Solution().process())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值