zoukankan      html  css  js  c++  java
  • 模板

    其实一般都只是求一个组合数:

    const ll MOD=1e9+7;
    const int MAXN=1e6;
    
    ll inv[MAXN+5],fac[MAXN+5],invfac[MAXN+5];
    
    void init_inv(int n=MAXN,ll mod=MOD) {
        inv[1]=1;
        for(int i=2; i<=n; i++) {
            inv[i]=inv[mod%i]*(mod-mod/i)%mod;
        }
    }
    
    void init_fac_invfac(int n=MAXN,ll mod=MOD) {
        init_inv(n);
        fac[0]=1,invfac[0]=1;
        for(int i=1; i<=n; i++) {
            fac[i]=fac[i-1]*i%mod;
            invfac[i]=invfac[i-1]*inv[i]%mod;
        }
    }
    
    inline ll C(ll n,ll m,ll mod=MOD) {
        if(n<m)
            return 0;
        return fac[n]*invfac[n-m]%mod*invfac[m]%mod;
    }
    

    相关知识点应在《组合数学》中寻找,而不是在模板中寻找。

    从旧模板的快速幂bug,逆元bug,排列数bug一路走来……

    update1:通过【模板】卢卡斯定理的验证。
    update2:优化了直接计算组合数的速度,(可能)优化了卢卡斯定理的退出条件。
    update3:增加了错位排序,D(n)表示n个数的排列个数,使得每个数都不在应在的位置,即A[i]!=i对所有i成立。
    update4:当模数比较小时,可能出现前缀积为0的情况,这种时候导致乘法逆元并不存在。会使得使用线性乘法逆元的组合数失效!但是卢卡斯定理可以正确约分掉!例如p=10007时!

    //特殊定义D[0]为1
        D[0]=1;
        D[1]=0;
        for(int i=2;i<=1000000;i++){
            if(i&1){
                D[i]=((ll)i*D[i-1]-1ll)%MOD;
                if(D[i]<0)
                    D[i]+=MOD;
            }
            else{
                D[i]=((ll)i*D[i-1]+1ll)%MOD;
            }
        }
    

    二项式反演

    可以表示成

    (f_n=sumlimits_{i=0}^{n}(−1)^iC_n^ig_i⇔g_n=sumlimits_{i=0}^{n}(−1)^iC_n^if_i)
    你会发现这个式子具有极强的对称性!另外一个更加常见的形式是
    (f_n=sumlimits_{i=0}^{n}C_n^ig_i⇔g_n=sumlimits_{i=0}^{n}(−1)^{n-i}C_n^if_i)


    注意事项:

    1.一些函数需要修改常量以及初始化
    2.不用到初始化时应回收空间,设MAXN=0即可。


    标准模板,卢卡斯定理默认装载重新求组合数。

    ```cpp namespace combinatorics{ //注意需要init(),必要时修改常量
    const ll MOD=1e9+7;
    const int MAXN=2000000;
    
    ll inv[MAXN+5],fac[MAXN+5],invfac[MAXN+5];
    
    //1. 快速幂 x^n %mod
    inline ll qpow(ll x,ll n,ll mod=MOD) {
        ll res=1%mod;
        while(n) {
            if(n&1)
                res=res*x%mod;
            x=x*x%mod;
            n>>=1;
        }
        return res;
    }
    
    //2. 快速乘 a*b %mod 防止乘法溢出ll
    inline ll qmut(ll a,ll b,ll mod=MOD) {
        ll res=0;
        while(b) {
            if(b&1)
                res=(res+a)%mod;
            a=(a+a)%mod;
            b>>=1;
        }
        return res;
    }
    
    //3. 乘法逆元 快速幂+费马小定理,要求p必须是质数 (依赖1. 快速幂)
    inline ll inv_p(ll n,ll p=MOD) {
        return qpow(n,p-2,p);
    }
    
    //4. 扩展欧几里得算法:返回 g=gcd(a,b) ,以及对应的等式 ax+by=g 的解
    ll exgcd(ll a,ll b,ll &x,ll &y) {
        if(!a&&!b)
            return -1;
        if(!b) {
            x=1,y=0;
            return a;
        }
        ll d=exgcd(b,a%b,y,x);
        y-=a/b*x;
        return d;
    }
    
    //5. 扩展欧几里得算法求逆元,只要求 a,m 互质
    inline ll inv_rp(ll a,ll mod=MOD) {
        ll x,y;
        ll d=exgcd(a,mod,x,y);
        if(d==1)
            return (x%mod+mod)%mod;
        return -1;
    }
    
    //6. 线性求乘法逆元
    void init_inv(int n=MAXN,ll mod=MOD) {
        inv[1]=1;
        for(int i=2; i<=n; i++) {
            inv[i]=inv[mod%i]*(mod-mod/i)%mod;
        }
    }
    
    //7. 线性求阶乘,阶乘乘法逆元 (依赖6. 线性求乘法逆元)
    void init_fac_invfac(int n=MAXN,ll mod=MOD) {
        .//这个点用来触发编译错误,提示使用init(),并修改MAXN和MOD
        init_inv(n);
        fac[0]=1,invfac[0]=1;
        for(int i=1; i<=n; i++) {
            fac[i]=fac[i-1]*i%mod;
            invfac[i]=invfac[i-1]*inv[i]%mod;
        }
    }
    
    //8. 利用阶乘和阶乘逆元计算排列数A_n^m %mod (依赖7. 线性求阶乘,阶乘乘法逆元)
    inline ll A(ll n,ll m,ll mod=MOD) {
        return fac[n]*invfac[n-m]%mod;
    }
    
    //9. 直接计算排列数A_n^m %mod
    ll A_2(ll n,ll m,ll mod=MOD){
        if(m>n) return 0;
        ll u=1;
        for(int i=n-m+1;i<=n;i++)
            u=u*i%mod;
        return u;
    }
    
    //10. 利用阶乘和阶乘逆元计算组合数C_n^m %mod (依赖7. 线性求阶乘,阶乘乘法逆元)
    inline ll C(ll n,ll m,ll mod=MOD) {
        if(n<m)
            return 0;
        return fac[n]*invfac[n-m]%mod*invfac[m]%mod;
    }
    
    //11. 直接计算组合数C_n^m %mod
    inline ll C_2(ll n,ll m,ll mod=MOD){
        if(n<m) 
            return 0;
        //组合数对称优化
        m=min(m,n-m);
        ll u=1,d=1;
        for(int i=n-m+1;i<=n;i++)
            u=u*i%mod;
        for(int i=1;i<=m;i++)
            d=d*i%mod;
    
        //为下面的inv装入正确的乘法逆元,默认使用扩展欧几里得算法重新求解
        //可以视情况换用更快的init()后的inv[d],或者费马小定理(当mod为质数时费马小定理更简单)
        return u*inv_rp(d,mod)%mod;
    }
    
    //12. 卢卡斯定理计算组合数C_n^m%p,p是质数 (依赖10. /11. 计算组合数)
    inline ll Lucas(ll n,ll m,ll p=MOD) {
        if(m>n)
            return 0;
        ll ans=1;
        //当ans为0之后就可以返回了
        while(m&&ans){
            //当p并非默认参数MOD时,必须使用直接计算组合数的C_2
            ans=ans*C_2(n%p,m%p,p)%p;
            //当p为默认参数MOD时,使用init()后的O(1)求组合数
            //ans=ans*C(n%p,m%p)%p;
            n/=p,m/=p;
        }
        return ans;
    }
    

    };

    using namespace combinatorics;
    //注意需要init(),必要时修改常量

    </details>
    
    
    ---
    精简模板,需要保证固定MOD为质数。
    <details>
    ```cpp
    namespace combinatorics{
        //注意需要init(),必要时修改常量
    
        const ll MOD=1e9+7;
        const int MAXN=2000000;
    
        ll inv[MAXN+5],fac[MAXN+5],invfac[MAXN+5];
    
        //1. 快速幂 x^n %mod
        inline ll qpow(ll x,ll n,ll mod=MOD) {
            ll res=1%mod;
            while(n) {
                if(n&1)
                    res=res*x%mod;
                x=x*x%mod;
                n>>=1;
            }
            return res;
        }
    
        //3. 乘法逆元 快速幂+费马小定理,要求p必须是质数 (依赖1. 快速幂)
        inline ll inv_p(ll n,ll p=MOD) {
            return qpow(n,p-2,p);
        }
    
        //6. 线性求乘法逆元
        void init_inv(int n=MAXN,ll mod=MOD) {
            inv[1]=1;
            for(int i=2; i<=n; i++) {
                inv[i]=inv[mod%i]*(mod-mod/i)%mod;
            }
        }
    
        //7. 线性求阶乘,阶乘乘法逆元 (依赖6. 线性求乘法逆元)
        void init_fac_invfac(int n=MAXN,ll mod=MOD) {
            .//这个点用来触发编译错误,提示使用init(),并修改MAXN和MOD
            init_inv(n);
            fac[0]=1,invfac[0]=1;
            for(int i=1; i<=n; i++) {
                fac[i]=fac[i-1]*i%mod;
                invfac[i]=invfac[i-1]*inv[i]%mod;
            }
        }
    
        //8. 利用阶乘和阶乘逆元计算排列数A_n^m %mod (依赖7. 线性求阶乘,阶乘乘法逆元)
        inline ll A(ll n,ll m,ll mod=MOD) {
            return fac[n]*invfac[n-m]%mod;
        }
    
        //10. 利用阶乘和阶乘逆元计算组合数C_n^m %mod (依赖7. 线性求阶乘,阶乘乘法逆元)
        inline ll C(ll n,ll m,ll mod=MOD) {
            return fac[n]*invfac[n-m]%mod*invfac[m]%mod;
        }
    
    };
    
    
    using namespace combinatorics;
    //注意需要init(),必要时修改常量
    

    扩展卢卡斯定理要在别的随笔中找。

  • 相关阅读:
    c++ 函数中的部分代码执行一次
    如何限制对象只能建立在堆上或者栈上
    FFMPEG Qt视频播放器
    C/C++中带可变参数的函数
    柔性数组
    压缩图片网站
    vscode存盘时格式化
    两个i标签之间有缝隙
    node 中process进程argv,argv0,execArgv,execPath
    chalk插件 使终端输出的字带颜色
  • 原文地址:https://www.cnblogs.com/Yinku/p/10703338.html
Copyright © 2011-2022 走看看