任意mod二項係数

計算量の異なる \(2\) つのコードがある

github

  • その1
    • \(n,r \leq 10^{18}, mod \leq 10^6\) 程度の場合に計算できる
  • その2
    • \(n,r \leq 10^6, mod \leq 10^9\) 程度の場合に計算できる

その1

init

  • mod \(M\)を与えると、combを実行するのに必要な数値を前計算する
  • 計算量は \(M\) の素因数分解を \(\prod_{i=1}^{\omega(M)}{ {p_i}^{e_i}}\) とすると \(Ο( \sum_{i=1}^{\omega(M)}{ {p_i}^{e_i}} + \sqrt{M})\)
    • \(\omega(m)\) は \(m\) の素因数の個数、\(m \leq 10^9\) のとき \(\omega(m) \leq 10\)

composite_binomial_coefficient_1

  • コンストラクタ。initを呼ぶ

comb

  • 整数\(n,r\)を与えると、\(\frac{n!}{r!(n-r)!}\)を\(M\)で割った余りを返す
  • extensionはないため、\(n\) にinitで与えた \(N\) より大きい値を与えた場合の挙動は保証しない
  • 計算量は\(Ο( (\log n + \log M) \omega(M) )\)
class composite_binomial_coefficient_1{
    // Copyright (c) 2023 0214sh7
    // https://github.com/0214sh7/library/
    private:
    long long Mod;    
    std::vector<std::array<long long,3>> P;
    std::vector<std::vector<long long>> fact,factinv;

    std::pair<long long,long long> bezout(long long a,long long b){
        if(b==0){
            return {1,0};
        }
        long long r = a%b, q = (a-r)/b;
        std::pair<long long,long long> D = bezout(b,r);
        return {D.second,D.first-q*D.second};
    }

    public:

    void init(long long M){

        if(M<1){
            return;
        }

        Mod = M;

        //Mを素因数分解する
        //Pは{素数,個数,素数^個数}
        P.clear();
        long long m = M;
        for(int i=2;i*i<=m;i++){
            int v = 1;
            int e = 0;
            while(m%i==0){
                e++;
                m /= i;
                v *= i;
            }
            if(e>0){
                P.push_back({i,e,v});
            }
        }
        if(m!=1){
            P.push_back({m,1,m});
        }

        //用いる素数pについて、(x!)_p := (1~xでpと互いに素なものの積)を求める
        fact.clear();
        fact.resize(P.size());
        factinv.clear();
        factinv.resize(P.size());

        for(int i=0;i<(int)P.size();i++){
            fact[i].resize(P[i][2]);
            fact[i][0] = 1;
            for(int j=1;j<P[i][2];j++){
                if(j%P[i][0]==0){
                    fact[i][j] = fact[i][j-1];
                }else{
                    fact[i][j] = (fact[i][j-1]*j)%P[i][2];
                }
            }

            factinv[i].resize(P[i][2]);
            factinv[i][P[i][2]-1] = bezout(fact[i][P[i][2]-1],P[i][2]).first;
            if(factinv[i][P[i][2]-1]<0)factinv[i][P[i][2]-1] += P[i][2];
            
            for(int j=P[i][2]-2;j>=0;j--){
                if((j+1)%P[i][0]==0){
                    factinv[i][j] = factinv[i][j+1];
                }else{
                    factinv[i][j] = (factinv[i][j+1]*(j+1))%P[i][2];
                }
            }
        }
    }

    composite_binomial_coefficient_1(long long mod){
        init(mod);
    }

    long long comb(long long n,long long r){
        
        if(n<0 || r<0 || n<r || Mod==1){
            return 0;
        }
        long long k = n-r;

        //各p^qについてnCr mod p^qを求めてLにまとめる
        std::vector<std::pair<long long,long long>> L(P.size());

        for(int i=0;i<(int)P.size();i++){
            long long p = P[i][0], q = P[i][1], pq = P[i][2];

            std::vector<long long> N,K,R;
            long long nown = n, nowk = k, nowr = r;
            while(nown>0){
                N.push_back(nown%pq);
                K.push_back(nowk%pq);
                R.push_back(nowr%pq);
                nown /= p;
                nowk /= p;
                nowr /= p;
            }

            long long e0 = 0, e1 = 0;
            nown = n/p, nowk = k/p, nowr = r/p;
            while(nown > 0){
                e0 += nown;
                e0 -= nowk;
                e0 -= nowr;
                nown /= p;
                nowk /= p;
                nowr /= p;
            }
            nown = n/pq, nowk = k/pq, nowr = r/pq;
            while(nown > 0){
                e1 += nown;
                e1 -= nowk;
                e1 -= nowr;
                nown /= p;
                nowk /= p;
                nowr /= p;
            }
            
            long long T = 1;
            if((p!=2 || q<3) && e1%2 == 1){
                T = pq-1;
            }

            for(int j=0;j<(int)N.size();j++){
                T = (T*fact[i][N[j]])%pq;
                T = (T*factinv[i][K[j]])%pq;
                T = (T*factinv[i][R[j]])%pq;
            }

            for(int j=0;j<std::min(q,e0);j++){
                T = (T*p)%pq;
            }

            L[i] = {T,pq};
        }

        //中国剰余定理を用いて復元する
        std::pair<long long,long long> C = L[0];
        for(int i=1;i<(int)P.size();i++){
            long long q = C.second*L[i].second;
            std::pair<long long,long long> u = bezout(C.second,L[i].second);
            long long c = (((C.first*L[i].second)%q)*u.second)%q;
            long long d = (((L[i].first*C.second)%q)*u.first)%q;
            C.first = (c+d+q)%q;
            C.second = q;
        }

        return C.first;
    }
};

使用例


実行コード

#include <bits/stdc++.h>

class composite_binomial_coefficient_1{/*省略*/};

int main(void){
    
    std::cout << "10で割ったあまり" << std::endl; 
    composite_binomial_coefficient_1 CBC(10);
    for(int i=0;i<=8;i++){
        for(int j=0;j<=i;j++){
            std::cout << CBC.comb(i,j) << " ";
        }std::cout << std::endl;
    }
    std::cout << std::endl;

    std::cout << "もとの値" << std::endl;
    composite_binomial_coefficient_1 CBC_origin(1000);
    for(int i=0;i<=8;i++){
        for(int j=0;j<=i;j++){
            std::cout << CBC_origin.comb(i,j) << " ";
        }std::cout << std::endl;
    }
    
    return 0;
}

出力

10で割ったあまり
1 
1 1 
1 2 1 
1 3 3 1 
1 4 6 4 1 
1 5 0 0 5 1 
1 6 5 0 5 6 1 
1 7 1 5 5 1 7 1 
1 8 8 6 0 6 8 8 1 

もとの値
1 
1 1 
1 2 1 
1 3 3 1 
1 4 6 4 1 
1 5 10 10 5 1 
1 6 15 20 15 6 1 
1 7 21 35 35 21 7 1 
1 8 28 56 70 56 28 8 1 

更新履歴


日時 内容
2023/06/29 ライセンスのコメントアウトを変更
2023/03/18 任意mod二項係数を追加

確認した問題

問題 提出
Library Checker 提出

その2

init

  • 整数\(N\)とmod \(M\)を与えると、combを実行するのに必要な数値を前計算する
  • 計算量は \(Ο(N \log N + N \omega(M) + \sqrt{M})\)
    • \(\omega(m)\) は \(m\) の素因数の個数、\(m \leq 10^9\) のとき \(\omega(m) \leq 10\)

composite_binomial_coefficient_2

  • コンストラクタ。initを呼ぶ

comb

  • 整数\(n,r\)を与えると、\(\frac{n!}{r!(n-r)!}\)を\(M\)で割った余りを返す
  • extensionはないため、\(n\) にinitで与えた \(N\) より大きい値を与えた場合の挙動は保証しない
  • 計算量は\(Ο(\log M + \omega(M) )\)
    • 特に、 \(n,r\) に依存しない
class composite_binomial_coefficient_2{
    // Copyright (c) 2023 0214sh7
    // https://github.com/0214sh7/library/
    private:
    int sze = 0;
    long long Mod;
    std::vector<long long> P;
    std::vector<long long> d,prime;
    std::vector<int> index;
    std::vector<long long> factPri;
    std::vector<std::vector<long long>> factRel;
    std::vector<std::vector<long long>> power;

    std::pair<long long,long long> bezout(long long a,long long b){
        if(b==0){
            return {1,0};
        }
        long long r = a%b, q = (a-r)/b;
        std::pair<long long,long long> D = bezout(b,r);
        return {D.second,D.first-q*D.second};
    }

    public:

    void init(int N,long long M){

        if(N<0 || M<1){
            return;
        }

        if(M==1){
            Mod = M;
            return;
        }

        sze = N+1;
        Mod = M;
        
        //1~Nの非自明な約数を求める
        d.resize(N+1);
        std::fill(d.begin(),d.end(),0);
        prime.clear();
        index.resize(N+1);
        std::fill(index.begin(),index.end(),-1);

        if(N>0){
            d[1] = 1;
        }
        for(long long i=2;i<=N;i++){
            if(d[i]==0){
                d[i]=i;
                prime.push_back(i);
            }
            for(int j=0;j<(int)prime.size() && prime[j]<=d[i] && i*prime[j]<=N ;j++){
                d[prime[j]*i]=prime[j];
            }
        }
        
        //法が含む素数のリストを求める
        P.clear();
        for(int i=2;i*i<=M;i++){
            int e = 0;
            while(M%i==0){
                e++;
                M /= i;
            }
            if(e>0){
                if(i<=N)index[i] = P.size();
                P.push_back(i);
            }
        }
        if(M!=1){
            if(M<=N)index[M] = P.size();
            P.push_back(M);
        }

        //k!について、Mと互いに素である部分とそうでない部分に分けてそれぞれ求める
        factPri.resize(N+1);
        std::fill(factPri.begin(),factPri.end(),1);
        factRel.resize(N+1);
        for(int i=0;i<=N;i++){
            factRel[i].resize(P.size());
            std::fill(factRel[i].begin(),factRel[i].end(),0);
        }

        for(int i=2;i<=N;i++){

            factPri[i] = factPri[i-1];
            factRel[i] = factRel[i-1];

            long long now = i;
            while(now>1){
                long long p = d[now];
                if(index[p]==-1){
                    factPri[i] = (factPri[i]*p)%Mod;
                }else{
                    factRel[i][index[p]]++;
                }
                now /= p;
            }
        }

        //p^0,p^1,...,p^(N/p)を前計算する
        power.resize(P.size());
        for(int i=0;i<(int)P.size();i++){
            power[i].resize(1+N/P[i]);
            power[i][0] = 1;
            for(int j=1;j<1+N/P[i];j++){
                power[i][j] = (power[i][j-1]*P[i])%Mod;
            }
        }

    }

    composite_binomial_coefficient_2(int N,long long M){
        init(N,M);
    }

    long long comb(long long n,long long r){
        if(Mod==1){
            return 0;
        }
        if(n<0 || r<0 || n<r){
            return 0;
        }

        long long R = factPri[n];
        long long m = bezout(factPri[r],Mod).first;
        if(m<0)m+=Mod;
        R = (R*m)%Mod;
        m = bezout(factPri[n-r],Mod).first;
        if(m<0)m+=Mod;
        R = (R*m)%Mod;

        for(int i=0;i<(int)P.size();i++){
            int e = factRel[n][i] - factRel[r][i] - factRel[n-r][i];
            assert(e>=0);
            R = (R*power[i][e])%Mod;
        }

        return R;
    }

};

使用例


実行コード

#include <bits/stdc++.h>

class composite_binomial_coefficient_2{/*省略*/};

int main(void){
    
    std::cout << "10で割ったあまり" << std::endl; 
    composite_binomial_coefficient_2 CBC(8,10);
    for(int i=0;i<=8;i++){
        for(int j=0;j<=i;j++){
            std::cout << CBC.comb(i,j) << " ";
        }std::cout << std::endl;
    }
    std::cout << std::endl;

    std::cout << "もとの値" << std::endl;
    composite_binomial_coefficient_2 CBC_origin(8,1000);
    for(int i=0;i<=8;i++){
        for(int j=0;j<=i;j++){
            std::cout << CBC_origin.comb(i,j) << " ";
        }std::cout << std::endl;
    }
    
    return 0;
}

出力

10で割ったあまり
1 
1 1 
1 2 1 
1 3 3 1 
1 4 6 4 1 
1 5 0 0 5 1 
1 6 5 0 5 6 1 
1 7 1 5 5 1 7 1 
1 8 8 6 0 6 8 8 1 

もとの値
1 
1 1 
1 2 1 
1 3 3 1 
1 4 6 4 1 
1 5 10 10 5 1 
1 6 15 20 15 6 1 
1 7 21 35 35 21 7 1 
1 8 28 56 70 56 28 8 1 

更新履歴


日時 内容
2023/06/29 ライセンスのコメントアウトを変更
2023/03/18 任意mod二項係数を追加

確認した問題

問題 提出
ARC012-D 提出