【BZOJ 3907】网格(Catalan数)

本文介绍了一种计算特定组合数的方法,该组合数形式与Catalan数类似。通过阶乘分解质因数并利用快速幂进行高效计算,避免了高精度除法带来的复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接
这个题推导公式跟\(Catalan\)数是一样的,可得解为\(C_{n+m}^n-C_{n+m}^{n+1}\)
然后套组合数公式\(C_n^m=\frac{n!}{m!(n-m)!}\)
用阶乘分解的方法对分子和分母分解质因数然后指数相减,最后把剩下的高精度乘起来就行了,这样就避免了高精除法。可以用快速幂,但我太lan了,就直接暴力乘起来了。
说一下怎么阶乘分解,直接对每个数分解质因数的时间复杂度是\(O(n\sqrt{n})\),这显然是不可忍受的。
于是,考虑先用线筛求出\(1~n\)之间所有质数,然后枚举所有质数\(p\)\(1~n\)中所有\(p\)的倍数包含一个质因子\(p\),所有\(p^2\)的倍数包含一个质因子\(p\),...
所以,\(n!\)\(p\)的指数为\(\sum_{i=1}^{\left\lfloor \log_p n\right\rfloor}\left\lfloor n/p^i \right\rfloor\)
枚举\(p\)求就行了,时间复杂度\(O(n\ log\ n)\)

Code:

#include <cstdio>
#include <cstring>
const int MAXN = 10010;
int prime[MAXN], v[MAXN], c[MAXN], d[MAXN];
int n, cnt, m;
const int MOD = 10000;
struct Int{
    int s[MAXN];
    Int(int x){ memset(s, 0, sizeof s); s[++s[0]] = x % 10, x /= 10; } 
    void mul(int x){
        s[1] *= x;
        for(int i = 2; i <= s[0]; ++i){
           s[i] = s[i] * x + s[i - 1] / MOD;
           s[i - 1] %= MOD;
        }
        while(s[s[0]] >= MOD){
          s[++s[0]] = s[s[0] - 1] / MOD;
          s[s[0] - 1] %= MOD;
        }
    }
    void cut(Int x){
        for(int i = 1; i <= x.s[0]; ++i)
           s[i] -= x.s[i];
        for(int i = 1; i <= x.s[0]; ++i){
           if(s[i] < 0){
             s[i] += MOD;
             --s[i + 1];
           }
        }
        if(!s[s[0]]) --s[0];
    }
    void print(){
        printf("%d", s[s[0]]);
        for(int i = s[0] - 1; i; --i)
           printf("%04d", s[i]);
    }
}a(1), b(1);
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 2; i <= n + m; ++i){
       if(!v[i]){
         v[i] = i;
         prime[++cnt] = i;
       }  
       for(int j = 1; j <= cnt; ++j){
          if(prime[j] > v[i] || prime[j] * i > n + m) break;
          v[prime[j] * i] = prime[j];
       }
    }
    for(int i = 1; i <= cnt; ++i)
       for(int j = prime[i]; j <= n + m; j *= prime[i])
          c[i] += (n + m) / j;
    for(int i = 1; i <= cnt && prime[i] <= n; ++i)
       for(int j = prime[i]; j <= n; j *= prime[i])
          c[i] -= n / j;
    for(int i = 1; i <= cnt && prime[i] <= m; ++i)
       for(int j = prime[i]; j <= m; j *= prime[i])
          c[i] -= m / j;
    for(int i = 1; i <= cnt; ++i)
       for(int j = 1; j <= c[i]; ++j)
          a.mul(prime[i]);
          
    for(int i = 1; i <= cnt; ++i)
       for(int j = prime[i]; j <= n + m; j *= prime[i])
          d[i] += (n + m) / j;
    for(int i = 1; i <= cnt && prime[i] <= n + 1; ++i)
       for(int j = prime[i]; j <= n + 1; j *= prime[i])
          d[i] -= (n + 1) / j;
    for(int i = 1; i <= cnt && prime[i] <= m - 1; ++i)
       for(int j = prime[i]; j <= m - 1; j *= prime[i])
          d[i] -= (m - 1) / j;
    for(int i = 1; i <= cnt; ++i)
       for(int j = 1; j <= d[i]; ++j)
          b.mul(prime[i]);
    a.cut(b);
    a.print();
    return 0;
}

转载于:https://www.cnblogs.com/Qihoo360/p/9478043.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值