组合计数

目录
  • 组合计数
    • 1. 算法分析
      • 1.1 组合数/排列数
      • 1.2 错排数
      • 1.3 卡特兰数
    • 2. 板子
      • 2.1 a、b小(a、b~1e4),模数大
      • 2.2 a、b大(a、b~1e8),模数大
      • 2.3 a、b大(a、b~1e18),模数小
      • 2.4 a、b大(a、b~1e7),模数没有
    • 3. 例题
      • 3.1 组合数/排列数/乘法原理/加法原理
      • 3.2 错排数
      • 3.3 卡特兰数

组合计数

1. 算法分析

1.1 组合数/排列数

C[a][b]:从a里面选b个的方案

递推:C[a][b] = C[a-1][b]+C[a-1][b-1] 可以处理到a,b属于[1, 1e4]
公式:C[a][b] = fact[a] * infact[b] * infact[a - b] (fact[a]表示a的阶乘,infact[a]表示a!的逆元)可以处理到a,b属于[1, 1e8]
lucas定理:C[a][b]=C[a%p][b%p] * C[a/p][b/p] 适用于a和b很大,但是p比较小的情景

组合恒等式:
C[n][0]+C[n][1]+...+C[n][n] = 2 ^ n

排列数
A[a][b] = fact[a!] / fact[(a-b)!]

1.2 错排数

定义: 假设有n个元素,n个位置,每个元素都有自己唯一的正确位置,问,所有元素都处在错误位置有多少可能
公式
设f(n) 表示n个元素的错排种数,则f(n)=(n-1)∗(f(n - 1)+f(n − 2))
f(0) = 1, f(1) = 0, f(2) = 1

1.3 卡特兰数

卡特兰数序列

H0 H1 H2 H3 H4 H5 H6
1 1 2 5 14 42 132

公式
$ H(n) = \frac{C[2n][n]}{n+1} (n>=2, n∈N+)$

H(n)=\begin{cases}
\sum_{i=1}^n H(i-1)H(n-i), n>=2, n∈N+\\
1, n = 0, 1
\end{cases}

$ H(n) = \frac{H(n-1) * (4n - 2)}{n+1} (n>=2, n∈N+)\( \) H(n) = C[2n][n] - C[2n][n-1]$

处理的问题

  1. 有2n个人排成一行进入剧场。入场费 5 元。其中只有n个人有一张 5 元钞票,另外 n 人只有 10 元钞票,剧院无其它钞票,问有多少中方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零?
  2. 一位大城市的律师在她住所以北 n 个街区和以东 n 个街区处工作。每天她走 2n 个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
  3. 在圆上选择 2n 个点,将这些点成对连接起来使得所得到的 n 条线段不相交的方法数?
  4. 对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方法数?
  5. 一个栈(无穷大)的进栈序列为 1,2,3,...,n 有多少个不同的出栈序列?
  6. n个结点可够造多少个不同的二叉树?
  7. n 个不同的数依次进栈,求不同的出栈结果的种数?
  8. n 个 +1 和 n 个 -1 构成 2n 项 a1,a2,...,a2n ,其部分和满足 a1+a2+...+ak>=0 对与 n 该数列为?

2. 板子

2.1 a、b小(a、b~1e4),模数大

/*
本题的数据量比较小,a和b均为[1, 2000],询问1e4次
可以先把c[a][b]预处理出来,计算4e6次,然后每次O(1)得到答案
*/
#include

using namespace std;

int const N = 2e3 + 10, mod = 1e9 + 7;
int c[N][N];  // 记录答案
int n;

// 预处理
void init()
{
    for (int i = 0; i < N; ++i)
        for (int j = 0; j <= i; ++j)
            if (j == 0) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}

int main()
{
    init();  // 预处理
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        printf("%d\n", c[a][b]);
    }
    return 0;
}

2.2 a、b大(a、b~1e8),模数大

/*
本题的a,b数据量为[1, 1e5],可以预处理出阶乘,fact[a]表示a的阶乘,infact[a]表示a!的逆元
那么    fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
每次询问是可以按照公式fact[a] * infact[b] * infact[a - b]来O(1)计算出C[a][b]
*/
#include

using namespace std;

typedef long long LL;

int const N = 1e5 + 10, mod = 1e9 + 7;
int fact[N], infact[N];
int n;

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init()
{
    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) % mod;
    }
}

int main()
{
    init();  // 预处理

    // 计算
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);
    }
    return 0;
}

2.3 a、b大(a、b~1e18),模数小

/*
本题的a,b数据量为[1, 1e18],但是p的数据量为[1, 1e5]
那么可以使用lucas来处理,C[a][b] = C[a % p][b % p] * C[a / p][b / p]
每次询问的时候可以先预处理出fact[a],infact[a]等参数,然后用lucas公式来处理,
这样时间复杂度为20*1e5*60
*/
#include

using namespace std;

typedef long long LL;

int const N = 1e5 + 10;
LL fact[N], infact[N];
int n;

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init(LL p)
{
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; ++i)
    {
        fact[i] = fact[i - 1] * i % p;
        infact[i] = infact[i - 1] * qmi(i, p - 2, p) % p;
    }
}

// 计算C[a][b]
LL C(LL a, LL b, LL p)
{
    return fact[a] * infact[b] % p * infact[a - b] % p;
}

// lucas来处理,适用于p比较小的情况
LL lucas(LL a, LL b, LL p)
{
    if (a < p && b < p) return C(a, b, p);
    else return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}

int main()
{
    // 计算
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        memset(fact, 0, sizeof fact);
        memset(infact, 0, sizeof infact);
        LL a, b, p;
        scanf("%lld %lld %lld", &a, &b, &p);
        init(p);  // 预处理出fact[a], infact[a]等数据
        printf("%lld\n", lucas(a, b, p));
    }
    return 0;
}

2.4 a、b大(a、b~1e7),模数没有

/*
本题没有取模,那么C[a][b]按照公式算出来会很大,因此需要高精度
然后要是按照公式C[a][b]=(a!) / (b!(a - b)!)这样子算法会很慢,所以考虑把阶乘质因数分解,然后对于
每个质因子的乘积用高精度去运算,这样的时间复杂度为O(n)
*/
#include 
using namespace std;

typedef long long LL;
int const N = 5e3 + 10;
int prime[N], cnt, st[N];

// 筛素数
void init(int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (!st[i]) prime[cnt++] = i;
        for (int j = 0; prime[j] <= n / i; ++j)
        {
            st[prime[j] * i] = 1;
            if (i % prime[j] == 0) break;
        }
    }
}

// C = A * n
vector mul(vector &A, int n)
{
    vector C;  // 存储答案
    int t = 0;
    for (int i = 0; i < A.size() || t; ++i)
    {
        if (i < A.size()) t += A[i] * n;
        C.push_back(t % 10);  // 放入当前位的数字
        t /= 10;
    }
    return C;
}   

// 计算阶乘分解:这个算的就是C[a][b]
vector fac(int a, int b)
{
    int c = a - b;
    
    vector res;
    res.push_back(1);
    for (int i = 0; i < cnt; ++i)
    {
        int p = prime[i];  // 当前这个质因子为p
        int s = 0;  // 计算当前这个质因子的次数
        for (int j = a; j; j /= p) s += j / p;  // a里面p的个数
        for (int j = b; j; j /= p) s -= j / p;  // 减掉b里面p的个数
        for (int j = c; j; j /= p) s -= j / p;  // 减掉a-b里面p的个数
        for (int j = 0; j < s; ++j) res = mul(res, p);
    }
    return res;
}

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while(k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

int main()
{
    int a, b;
    cin >> a >> b;
    init(N);  // 预处理得到质因子
    vector res = fac(a, b);   // 计算阶乘
    for (int i = res.size() - 1; i >= 0; --i) cout << res[i];
    return 0;
}

3. 例题

3.1 组合数/排列数/乘法原理/加法原理

acwing1309车的放置
组合计数_第1张图片

/*
可以把一个网格分割成两个矩形,这样子第一个矩形的长和宽位a和b,第二个矩形的长和宽位(a + c)和d
然后答案即为  累加(1~k)C[b][i]*A[a][i]*C[d][k-i]*A[a+c-i][k-i]
*/
#include

using namespace std;

typedef long long LL;

const int N = 2010, mod = 100003;

int fact[N], infact[N];

int qmi(int a, int k)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % mod;
        a = (LL)a * a % mod;
        k >>= 1;
    }
    return res;
}

int C(int a, int b)
{
    if (a < b) return 0;
    return (LL)fact[a] * infact[a - b] % mod * infact[b] % mod;
}

int A(int a, int b)
{
    if (a < b) return 0;
    return (LL)fact[a] * infact[a - b] % mod;
}

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 a, b, c, d, k;
    cin >> a >> b >> c >> d >> k;

    // 计算答案
    int res = 0;
    for (int i = 0; i <= k; i ++ )
    {
        res = (res + (LL)C(b, i) * A(a, i) % mod * C(d, k - i) % mod * A(a + c - i, k - i)) % mod;
    }

    cout << res << endl;

    return 0;
}

acwing1308方程的解
对于不定方程 a1+a2+⋯+ak−1+ak=g(x),其中 k≥1 且 k∈N∗,x 是正整数,g(x)=xxmod 1000(即 xx 除以 1000 的余数),x,k 是给定的数。
k~100, x~int, k≤g(x)

/* 可以看出来g(x)的范围只到1000,因此就像1000个小球去拿隔板分割
可以插入隔板的地方有g(x)-1个,隔板有k-1块
然后组合计数C[g(x) - 1][k-1]计算即可 */

#include 
using namespace std;

int const N = 5e3 + 10;
int prime[N], cnt, st[N];

// 筛素数
void init(int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (!st[i]) prime[cnt++] = i;
        for (int j = 0; prime[j] <= n / i; ++j)
        {
            st[prime[j] * i] = 1;
            if (i % prime[j] == 0) break;
        }
    }
}

// C = A * n
vector mul(vector &A, int n)
{
    vector C;  // 存储答案
    int t = 0;
    for (int i = 0; i < A.size() || t; ++i)
    {
        if (i < A.size()) t += A[i] * n;
        C.push_back(t % 10);  // 放入当前位的数字
        t /= 10;
    }
    return C;
}   

// 计算阶乘分解:这个算的就是C[a][b]
vector fac(int a, int b)
{
    int c = a - b;
    
    vector res;
    res.push_back(1);
    for (int i = 0; i < cnt; ++i)
    {
        int p = prime[i];  // 当前这个质因子为p
        int s = 0;  // 计算当前这个质因子的次数
        for (int j = a; j; j /= p) s += j / p;  // a里面p的个数
        for (int j = b; j; j /= p) s -= j / p;  // 减掉b里面p的个数
        for (int j = c; j; j /= p) s -= j / p;  // 减掉a-b里面p的个数
        for (int j = 0; j < s; ++j) res = mul(res, p);
    }
    return res;
}

int k, x;

long long qmi(long long a, long long k, long long p)
{
    long long res = 1;
    while(k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

int main()
{
    int k, x;
    cin >> k >> x;
    int a, b;
    a = qmi(x, x, 1000) - 1, b = k - 1;
    init(N);  // 预处理得到质因子
    vector res;
    res = fac(a, b);   // 计算阶乘
    for (int i = res.size() - 1; i >= 0; --i) cout << res[i];
    return 0;
}

acwing1310数三角形
给定一个 n×m 的网格,请计算三点都在格点上的三角形共有多少个。
n、m~1e3

/*
总的三角形数目=所有网格点选三个点-横的-竖的-斜的
所有网格点选三个点=C((m + 1) * (n + 1), 3)
横的 = (m + 1) * C(n + 1)
竖的 = (n + 1) * C(m + 1)
斜的 = 2ll * (gcd(i, j) - 1) * (n - i + 1) * (m - j + 1)
斜的可以这样理解:最小单位就是gcd(i, j)=1的点对,一旦gcd(i, j)=k,那么就是表明(0, 0)和(i, j)之间有k个点,不包括(0, 0),(i, j)
*/
#include 

using namespace std;

typedef long long LL;
int n, m;

LL C(LL n) {
    return n * (n - 1) * (n - 2) / 6;
}

LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}

int main() {
    cin >> n >> m;
    LL res = C((m + 1) * (n + 1)) - (m + 1) * C(n + 1) - (n + 1) * C(m + 1);  // 总的-横的-竖的
    
    // 减去斜的
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            res -= 2ll * (gcd(i, j) - 1) * (n - i + 1) * (m - j + 1);
        }
    }
    cout << res;
    return 0;
}

acwing213古代猪文
给定整数n,q,计算q∑d|nC[n][d]mod999911659。
n、q~1e9

组合计数_第2张图片

#include 

using namespace std;

typedef long long LL;
int const N = 1e5 + 10, mod = 999911659;
LL fact[N][10], infact[N][10], factors[N], factors_cnt, prime[N], prime_cnt, a[N], m[N];
int n, q;

LL qmi(LL a, LL k, LL p) {
    LL res = 1 % p;
    while (k) {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init(LL p, int idx)
{
    fact[0][idx] = infact[0][idx] = 1;
    for (int i = 1; i < N; ++i)
    {
        fact[i][idx] = fact[i - 1][idx] * 1ll * i % p;
        infact[i][idx] = infact[i - 1][idx] * 1ll * qmi(i, p - 2, p) % p;
    }
}

// 计算C[a][b]
LL C(LL a, LL b, LL p, int idx)
{
    return fact[a][idx] * 1ll * infact[b][idx] % p * infact[a - b][idx] % p;
}

// lucas来处理,适用于p比较小的情况
LL lucas(LL a, LL b, LL p, int idx)
{
    if (a < p && b < p) return C(a, b, p, idx);
    else return C(a % p, b % p, p, idx) * lucas(a / p, b / p, p, idx) % p;
}

void get_divisors(int x)
{
    for (int i = 1; i <= x /i; ++i)  // 枚举到sqrtx(x)
    {
        if (x % i == 0)  // 如果能够整除
        {
            factors[factors_cnt++] = i;  // 放入i
            if (i != x / i) factors[factors_cnt++] = x / i;  // x/i不等于i,也放入答案中
        }
    }
    return;
}

LL exgcd(LL a, LL b, LL &x, LL &y)
{
    if (!b)  // b==0时,x=1, y=0
    {
        x = 1, y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x); // b != 0时,gcd(a, b) = gcd(b, a % b), x = x, y = y - a/b * x;
    y -= a / b * x;
    return d;
}

//a, m --- 第i个方程表示x ≡ ai(mod mi)
// n---- 方程个数
LL CRT( LL a[], LL m[], LL n )//x ≡ ai(mod mi)
{
    LL M = 1;
    for(int i = 0;i> n >> q;
    if (q == mod) {
        cout << 0 << endl;
        return 0;
    }
    get_divisors(n);  // 得到n的约数
    
    // 把999911659分解质因数结果
    prime_cnt = 4;
    prime[0] = 2, prime[1] = 3, prime[2] = 4679, prime[3] = 35617;
    
    // 得到CRT的模数
    for (int i = 0; i < prime_cnt; ++i) {
        m[i] = prime[i];
        init(prime[i], i);
    }
    
    // 得到CRT的ai
    for (int i = 0; i < prime_cnt; ++i) {
        LL t = 0;
        for (int j = 0; j < factors_cnt; ++j) {
            t = (t + lucas(n, factors[j], prime[i], i)) % prime[i] ;
        }
        a[i] = t;
    }
    
    // CRT得到: 累加[C[n][d], d|n]
    LL t = CRT(a, m, prime_cnt);
    cout << qmi(q, t, mod);
    return 0;
}

3.2 错排数

acwing230排列计数
求有多少种长度为 n 的序列 A,满足以下条件:
1、1 ~ n 这 n 个数在序列中各出现了一次。
2、若第 i 个数 A[i] 的值为 i,则称 i 是稳定的,序列恰好有 m 个数是稳定的。
由于满足条件的序列可能很多,所以请你将序列数对 1e9+7 取模后输出。

// 答案为:C[n][m]*f[n-m], f[i]为个数为i时的错排数
#include

using namespace std;

typedef long long LL;

int const N = 1e6 + 10, mod = 1e9 + 7;
int fact[N], infact[N], f[N];
int n, m, t;

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init()
{
    fact[0] = infact[0] = 1;
    f[0] = 1, f[1] = 0, f[2] = 1;
    for (int i = 1; i < N; ++i)
    {
        if (i >= 3) f[i] = (i - 1) * ((f[i - 1] + 0ll + f[i - 2]) % mod) % mod;
        fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
    }
}

int C(int a, int b) {
    return fact[a] * 1ll * infact[b] % mod * infact[a - b] % mod;
}

int main()
{
    init();  // 预处理

    cin >> t;
    while (t--) {
        scanf("%d%d", &n, &m);
        printf("%d\n", (C(n, m) * 1ll * f[n - m]) % mod);
    }
    return 0;
}

3.3 卡特兰数

acwing889满足条件的01序列
给定n个0和n个1,它们将按照某种顺序排成长度为2n的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中0的个数都不少于1的个数的序列有多少个。输出的答案对109+7取模。
n~1e5

// 任何一条经过y=x+1的上的点的线都可以投影到(n-1,n+1),因此方案数为c[2n][n]-c[2n][n-1]=c[2n][n]/(n+1)
#include

using namespace std;

typedef long long LL;

int const N = 2e5 + 10, mod = 1e9 + 7;
int fact[N], infact[N];
int n;

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init()
{
    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) % mod;
    }
}

int C(int a, int b) {
    return fact[a] * (LL)infact[b] % mod * infact[a - b] % mod;
}

int main()
{
    init();  // 预处理

    // 计算
    cin >> n;
    int res = (LL)fact[2 * n] * infact[n] % mod * infact[n] % mod * qmi(n + 1, mod - 2, mod) % mod;  // c[2n][n]-c[2n][n-1] = c[2n][n] / (n + 1)
    cout << res << endl;
    return 0;
}

acwing1315网格
某城市的街道呈网格状,左下角坐标为 A(0,0),右上角坐标为 B(n,m),其中 n≥m。现在从 A(0,0) 点出发,只能沿着街道向正右方或者正上方行走,且不能经过图示中直线左上方的点,即任何途径的点 (x,y) 都要满足 x≥y,请问在这些前提下,到达 B(n,m) 有多少种走法。
n,m~5000
组合计数_第3张图片

// 按照卡特兰数的推导方法,最后答案为:C(n + m, n) - C(n + m, m -1)
// 然后组合计数处理即可
#include 
using namespace std;

typedef long long LL;
int const N = 5e4 + 10;
int prime[N], cnt, st[N];

// 筛素数
void init(int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (!st[i]) prime[cnt++] = i;
        for (int j = 0; prime[j] <= n / i; ++j)
        {
            st[prime[j] * i] = 1;
            if (i % prime[j] == 0) break;
        }
    }
}

// C = A - B
vector  sub(vector &A, vector &B)
{
    vector  C;  // 用来存储答案
    int t = 0;  // 记录是否去高位借位
    for (int i = 0; i < A.size(); ++i)
    {
        t = A[i] - t;
        if (i < B.size() ) t -= B[i];  
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1; // 复原t
        else t = 0;
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back();  // 去除高位的前导0
    return C;
}

// C = A * n
vector mul(vector &A, int n)
{
    vector C;  // 存储答案
    int t = 0;
    for (int i = 0; i < A.size() || t; ++i)
    {
        if (i < A.size()) t += A[i] * n;
        C.push_back(t % 10);  // 放入当前位的数字
        t /= 10;
    }
    return C;
}   

// 计算阶乘分解:这个算的就是C[a][b]
vector fac(int a, int b)
{
    int c = a - b;
    
    vector res;
    res.push_back(1);
    for (int i = 0; i < cnt; ++i)
    {
        int p = prime[i];  // 当前这个质因子为p
        int s = 0;  // 计算当前这个质因子的次数
        for (int j = a; j; j /= p) s += j / p;  // a里面p的个数
        for (int j = b; j; j /= p) s -= j / p;  // 减掉b里面p的个数
        for (int j = c; j; j /= p) s -= j / p;  // 减掉a-b里面p的个数
        for (int j = 0; j < s; ++j) res = mul(res, p);
    }
    return res;
}

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while(k)
    {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

int main()
{
    int n, m;
    cin >> n >> m;
    init(N);  // 预处理得到质因子
    vector A = fac(n + m, n), B = fac(n + m, m - 1);  // 计算C(n+m, n)和C(n + m, m - 1)
    vector res = sub(A, B);   // 计算阶乘
    for (int i = res.size() - 1; i >= 0; --i) cout << res[i];
    return 0;
}

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

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

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

/*
分析本题,可以发现必须满足,前n项中,奇数项个数大于偶数项个数,那么满足卡特兰数性质
那么就是要要求C[2n][n]-C[2n][n-1] ,然后本题mod的数p可能和a不互质,因此a可能不存在逆元,可以考虑阶乘分解的算法
mod p只要在每次求完质因数的次数后调用快速幂,然后模掉即可
*/
#include

using namespace std;

typedef long long LL;

const int N = 2000010;

int n, p;
int primes[N], cnt;
bool st[N];

// 线性筛法
void init(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if  (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] * i <= n; j ++ )
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int qmi(int a, int k)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// 算每个质因子的次数
int get(int n, int p)
{
    int s = 0;
    while (n)
    {
        s += n / p;
        n /= p;
    }
    return s;
}

// 求C[a][b]
int C(int a, int b)
{
    int res = 1;
    for (int i = 0; i < cnt; i ++ )
    {
        int prime = primes[i];
        int s = get(a, prime) - get(b, prime) - get(a - b, prime);  // 算每个质因子的次数

        res = (LL)res * qmi(prime, s) % p;
    }

    return res;
}

int main()
{
    scanf("%d%d", &n, &p);
    init(n * 2);

    cout << (C(n * 2, n) - C(n * 2, n - 1) + p) % p << endl;

    return 0;
}

你可能感兴趣的:(组合计数)