求組合數總結

Educational Codeforces Round 83 (Rated for Div. 2)的D題所用到的Lucas定理,小編看了多篇博文後總結了一下,他其實是求組合數的一種方法,求組合數總共有以下幾種方法。
1.當數據範圍較小時,差不多1000左右,直接用遞歸打表求,這裏用到原理就是從n個球取m個球,相當於(取第n個,然後從前n-1個再取m-1個 + 不取第n個,從前n-1個取m個)即c[i][j] = (c[i - 1][j] + c[i - 1][j - 1])

for (int i = 0; i < N; i ++ )
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

2.當數據範圍中等,差不多是10e5 ,可以用逆元求解,爲什麼能用逆元求解,小編懶得碼字了,大家百度一下就行,原理也很簡單。
預處理出所有階乘取模的餘數fact[N]和所有階乘取模的逆元infact[N],這裏用到的公式就是C(a,b)=a! / b!(a-b)! ,這樣的話相當於a! * 逆元。,當p是質數的情況下直接用費馬小定理求解。

int qmi(int a, int k, int p)    // 快速冪模板
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// 預處理階乘的餘數和階乘逆元的餘數
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;
}
//結果=fact[a]*infact[b]

當數據範圍到1e9的時候,這時候就用到Lucas定理了。
內容:

C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

別問怎麼證,問就是不會,代碼也簡單 y總板子
這裏值得注意的就是,mod同樣不能太大

//若p是質數,則對於任意整數 1 <= m <= n,有:
  //  C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

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 C(int a, int b)     // 通過定理求組合數C(a, b)
{
    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2) % p;
    }
    return res;
}


int lucas(LL a, LL b)
{
    if (a < p && b < p) return C(a, b);
    return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}

上面都是取模一個數,假如不取模的時候還要去求C(n,m)時候,就用到了分解質因數法求組合數.板子還是用y總的,鏈接見上面。

/*當我們需要求出組合數的真實值,而非對某個數的餘數時,分解質因數的方式比較好用:
    1. 篩法求出範圍內的所有質數
    2. 通過 C(a, b) = a! / b! / (a - b)! 這個公式求出每個質因子的次數。 n! 中p的次數是 n / p + n / p^2 + n / p^3 + ...
    3. 用高精度乘法將所有質因子相乘*/

int primes[N], cnt;     // 存儲所有質數
int sum[N];     // 存儲每個質數的次數
bool st[N];     // 存儲每個數是否已被篩掉


void get_primes(int n)      // 線性篩法求素數
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}


int get(int n, int p)       // 求n!中的次數
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)       // 高精度乘低精度模板
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }

    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }

    return c;
}

get_primes(a);  // 預處理範圍內的所有質數

for (int i = 0; i < cnt; i ++ )     // 求每個質因數的次數
{
    int p = primes[i];
    sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}

vector<int> res;
res.push_back(1);

for (int i = 0; i < cnt; i ++ )     // 用高精度乘法將所有質因子相乘
    for (int j = 0; j < sum[i]; j ++ )
        res = mul(res, primes[i]);

總的來說,Lucas定理還是挺簡單的,一學就會一用就廢.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章