洛谷题单指南-组合数学与计数-P3200 [HNOI2009] 有趣的数列

原题链接:https://www.luogu.com.cn/problem/P3200

题意解读:1~2n的数排列,奇数位递增,偶数位递增,相邻奇数位<偶数位的数,求方案数。

解题思路:

以1 2 3 4 5 6 7 8为例,来挑选一半的数作为奇数位

1肯定是第一个奇数位,下一个奇数位可以是2,也可以是3,但不能是4,因为如果连续两个奇数位是1 4,那么2 3将无法满足相邻奇数位<偶数位的数。

这样以来,序列的前x项满足一个特性:作为奇数位的数个数 >= 作为偶数位的数个数

类比一下经典的路径问题:从0走到n,每次只能向上或者向下,路径中每个时刻,向上的次数不超过向下的次数。

这,就是经典的卡特兰数问题。

由于模p并不一定是素数不便于求逆,可以通过Catalan(n) = C(2n,n) - C(2n,n-1)计算,组合数可以针对(2n)!/(n!*n!),(2n)!/((n-1)!*(n+1)!)进行阶乘的素因子化简,然后快速幂即可求解。

素因子范围通过筛出2n以内所有素数即可。

小技巧:要计算n!中有多少个因子p,只需要计算n/p+n/p2+n/p3+...

100分代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1000005;

LL primes[N * 2], cnt;
bool vis[N * 2];
LL n, p;

//筛素数
void get_primes()
{
    for(LL i = 2; i <= 2 * n; i++)
    {
        if(!vis[i])
        {
            primes[++cnt] = i;
            for(LL j = i + i; j <= 2 * n; j += i)
                vis[j] = true;
        }
    }
}

//计算x!中素数p的幂次
LL get_p(LL x, LL p)
{
    LL res = 0;
    while(x)
    {
        res += x / p;
        x /= p;
    }
    return res;
}

LL ksm(LL a, LL b, LL mod)
{
    LL res = 1;
    while(b)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main()
{
    cin >> n >> p;
    get_primes(); // 2n以内的素数

    LL a = 1, b = 1;
    //a = C(2n,n)
    for(LL i = 1; i <= cnt; i++)
    {
        LL prime = primes[i];
        LL exp = get_p(2 * n, prime) - 2 * get_p(n, prime); 
        a = a * ksm(prime, exp, p) % p;
    }
    //b = C(2n,n-1)
    for(LL i = 1; i <= cnt; i++)
    {
        LL prime = primes[i];
        LL exp = get_p(2 * n, prime) - get_p(n - 1, prime) - get_p(n + 1, prime); 
        b = b * ksm(prime, exp, p) % p;
    }
    cout << (a - b + p) % p;
    return 0;
}

 

posted @ 2025-12-04 12:05  hackerchef  阅读(11)  评论(0)    收藏  举报