1. 程式人生 > 實用技巧 >hdu 3240

hdu 3240

題目連結

hdu3240

題目概述

        給出\(n\)個結點,最多\(n\)個結點可以組成的不同形態的二叉樹的數目.結果很大,所以輸出的結果要對\(m\)取模,其中\((1 \leq n \leq 100000), (1 \leq m \leq 100000000)\).

一點思路

        因為是最多用\(n\)個結點可以構成的總數,實際是1個結點,2個結點,\(\dots,n\)個結點可以構成的不同形態的二叉樹的數目之和.所以問題就轉化成求\(n\)個相同的結點可以構成的不同的二叉樹形態的數目.假設\(n\)個結點可以構成的二叉樹的\(f(n)\)個.對於\(n\)個相同的結點,首先有一個結點是根節點,然後對於剩下的\(n-1\)

個結點來說,講它們分成兩部分,分別構成左右子樹,可以分成\((0,n-1),(1,n-2),\dots(n-1,0)\)中,分別表示,左子樹由\(0\)個結點構成右子樹由\((n-1)\)個結點構成,左子樹由\(1\)個結點構成右子樹由\((n-2)\)個結點構成,\(\dots\),左子樹由\(n-1\)個結點構成右子樹由\(0\)個結點構成,由加法原理和乘法原理:

\[\begin{aligned} f(n) &= f(0)f(n-1)+f(1)f(n-2)+\cdots+f(n-1)f(0) \\ &= \sum_{i=0}^{n-1}{f(i)f(n-1-i)},\qquad f(0) = 1 \end{aligned} \]

這個恰好就是\(Catalan\)數的一個計算表示的方法.所以,\(n\)個相同結點可以構成的不同形態的二叉樹的數目恰好是\(C_n\).

        然後是解決計算\(Catalan\)數取模的問題.\(C_n\)的計算有三個公式:

\[\begin{aligned} C_n &= C_0C_{n-1}+C_1C_{n-2}+\cdots+C_{n-1}C_0 = \sum_{i=0}^{n-1}C_iC_{n-1-i}, \qquad C_0 = 1\\ C_n &= \frac{4*n-2}{n+1}C_{n-1}, \qquad C_0=1\\ C_0 &= \frac{1}{n+1}C_{2n}^n=\frac{(2n)!}{(n+1)!n!} \end{aligned} \]

        最初我用的是第一個式子計算提交的,然後TLE.然後第二個因為有一個分式,並且不能總保證\((4*n-2)\%(n+1)=0\)總成立,而\(C_{n-1}\)在前面的計算中已經取模了,很明顯如果直接分子分母取模再相除結果一定是不對的,所以我最初的想法是通過通過逆元把\(\frac{4*n-2}{n+1}\%m\)轉成\(((4*n-2)k)\%m\)來進行計算,其中的\(k\)\((n+1)\)\(m\)的逆,但是發現這個會出問題,因為\((n+1)k\equiv 1\, (mod \ m)\),只有當\(m\)是素數的條件下才成立,比如當\(n=1,m=100\)時,顯然是沒法求出這個逆元的.然後就一直卡在這裡了.┭┮﹏┭┮

進一步的想法

        對於模數\(m\),來說,滿足\(m=p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k},\, p_i\)是素數\(,a_i\ge1.\)所以可以先從\((4*n-2)\)\((n+1)\)中先剔除掉\(m\)的素因子,如果此時的\((n+1)\ !=1\)那麼可以通過求逆的方法,把原來的除法取模變成乘法逆元取模了.然後這個然後再乘上哪些提出掉的素因子再對\(m\)取模得到的就是\(C_n\%m\).

程式碼實現

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+5;
// ll S[N];

// 擴充套件gcd
void extend_gcd(ll a, ll b, ll& x, ll& y) {
    if(b == 0){
        x = 1;
        y = 0;
        return;
    }
    extend_gcd(b, a%b, x, y);
    ll temp = x;
    x = y;
    y = temp - (a / b) * y;
}

// 計算a模m的逆
ll mod_inverse(ll a, ll m){
    ll x, y;
    extend_gcd(a, m, x,y);
    return (m + x % m) % m;
}

// // 前項和乘積類加求解,TLE
// void calculate(int n, int m){
//     memset(S, 0, sizeof(S));
//     S[0] = 1;
//     ll ans = 0;
//     for (int i = 1; i <= n; ++i){
//         for (int j = 0; j < i; ++j){
//             S[i] += S[j]*S[i-1-j];
//             S[i] %= m;
//         }
//         ans += S[i];
//         ans %= m;
//     }
//     printf("%lld\n", ans);
// }

// 利用遞推式和逆元取模計算
void calculate(int n, int m){
    ll primes[N] = {0};
    int cnt = 0;
    ll nums[N] = {0};
    // 計算m的素因子
    ll temp = m;
    for (ll i = 2; i * i <= temp;++i){
        if(temp% i == 0){
            primes[cnt++] = i;
            // 剔除調m中的所有素因子i
            while(temp % i == 0){
                temp /= i;
            }
        }
    }
    // 判斷最後剩下m是不是一個素數
    if( temp != 1)
        primes[cnt++] = temp;
    ll pre = 1;
    ll ans = 0;
    ll cur = 1;
    for (int i = 1; i <= n; ++i){
        //去掉(4*i-2)中m的素因子,記錄這些素因子的個數
        ll k = 4 * i - 2;
        for (int j = 0; j < cnt; j++){
            // (4*i-2)中包含了m的某個素因子不止一次.
            while( k % primes[j] == 0){
                ++nums[j];
                k /= primes[j];
            }
        }
        pre *= k;
        pre %= m;
        // 去掉(i+1)中m的素因子,把這些素因子的個數從前面(4*i-2)中剔除
        k = i + 1;
        for (int j = 0; j < cnt; j++){
            // (4*i-2)中包含了m的某個素因子不止一次.
            while( k % primes[j] == 0){
                --nums[j];
                k /= primes[j];
            }
        }
        // 計算剔除素因子後(i+1)的逆元
        if( k != 1)
            k = mod_inverse(k, m);
        pre *= k;
        pre %= m;
        cur = pre;
        for (int j = 0; j < cnt; j++){
            // 乘以之前剔除的素因子
            for (int k = 0; k < nums[j]; ++k)
                cur = (cur * primes[j]) % m;
        }
        ans += cur;
        ans %= m;
    }
    printf("%lld\n", ans);
}

int main(int argc, const char** argv) {
    int n = 0, m = 0;
    while(scanf("%d%d", &n,&m) && (n+m != 0)) {
        calculate(n,m);
    }
    return 0;
}

        這裡面我覺得可能用一忽略的一個坑點就是那在求\(m\)的素因子,不僅僅求的是這一個素因子\(p_i\),還要把\(m\)中的\(p_i^{a_i}\)剔除掉,所以要不斷的除以這個素因子\(p_i\),直到不再包含.然後要分清楚這裡的cur也就是乘以當前剔除掉的素因子(分子分母相同的素因子會剔除掉一部分).

補充