zoukankan      html  css  js  c++  java
  • 组合计数

    组合计数

    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)=egin{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<bits/stdc++.h>
    
    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
    ", 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<bits/stdc++.h>
    
    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
    ", (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<bits/stdc++.h>
    
    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
    ", 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 <bits/stdc++.h>
    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<int> mul(vector<int> &A, int n)
    {
        vector<int> 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<int> fac(int a, int b)
    {
        int c = a - b;
        
        vector<int> 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<int> res = fac(a, b);   // 计算阶乘
        for (int i = res.size() - 1; i >= 0; --i) cout << res[i];
        return 0;
    }
    

    3. 例题

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

    acwing1309车的放置
    批注 2020-06-01 145550.png

    /*
    可以把一个网格分割成两个矩形,这样子第一个矩形的长和宽位a和b,第二个矩形的长和宽位(a + c)和d
    然后答案即为  累加(1~k)C[b][i]*A[a][i]*C[d][k-i]*A[a+c-i][k-i]
    */
    #include<bits/stdc++.h>
    
    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方程的解
    对于不定方程 a~1~+a~2~+⋯+a~k−1~+a~k~=g(x),其中 k≥1 且 k∈N∗,x 是正整数,g(x)=x^x^mod 1000(即 x^x^ 除以 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 <bits/stdc++.h>
    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<int> mul(vector<int> &A, int n)
    {
        vector<int> 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<int> fac(int a, int b)
    {
        int c = a - b;
        
        vector<int> 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<int> 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 <bits/stdc++.h>
    
    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

    Screenshot_20200604_003502_com.fluidtouch.noteshelf2 1.jpg

    #include <bits/stdc++.h>
    
    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;++i) M *= m[i];
        LL ret = 0;
        for(int i=0;i<n;++i)
        {
            LL x,y;
            LL tm = M/m[i];
            exgcd(tm,m[i],x,y);//调用外部函数:拓展欧几里得
            ret = (ret+tm*x*a[i])%M;
        }
        return (ret+M)%M;
    }
    
    
    int main() {
        cin >> 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<bits/stdc++.h>
    
    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
    ", (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<bits/stdc++.h>
    
    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
    批注 2020-06-02 092541.png

    // 按照卡特兰数的推导方法,最后答案为:C(n + m, n) - C(n + m, m -1)
    // 然后组合计数处理即可
    #include <bits/stdc++.h>
    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 <int> sub(vector<int> &A, vector<int> &B)
    {
        vector <int> 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<int> mul(vector<int> &A, int n)
    {
        vector<int> 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<int> fac(int a, int b)
    {
        int c = a - b;
        
        vector<int> 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<int> A = fac(n + m, n), B = fac(n + m, m - 1);  // 计算C(n+m, n)和C(n + m, m - 1)
        vector<int> 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<a3<⋯<a2n−1 ,所有的偶数项满足 a2<a4<⋯<a2n;
    3. 任意相邻的两项 a2i−1 与 a2i (1≤i≤n) 满足奇数项小于偶数项,即:a2i−1<a2i。

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

    /*
    分析本题,可以发现必须满足,前n项中,奇数项个数大于偶数项个数,那么满足卡特兰数性质
    那么就是要要求C[2n][n]-C[2n][n-1] ,然后本题mod的数p可能和a不互质,因此a可能不存在逆元,可以考虑阶乘分解的算法
    mod p只要在每次求完质因数的次数后调用快速幂,然后模掉即可
    */
    #include<bits/stdc++.h>
    
    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;
    }
    
  • 相关阅读:
    markdown样式代码保存
    【python系统学习08】for循环知识点合集
    【python系统学习07】一张图看懂字典并学会操作
    【python系统学习06】一张图看懂列表并学会操作
    java后端学习记录
    支付功能设计及实现思路
    《Kafka权威指南》读书笔记
    ReentrantLock源码简析
    敏捷开发流程
    上线新功能,如何兼容旧数据?
  • 原文地址:https://www.cnblogs.com/spciay/p/13060832.html
Copyright © 2011-2022 走看看