1. 程式人生 > >P2606 [ZJOI2010]排列計數

P2606 [ZJOI2010]排列計數

inline 質數 ++ class 前驅 就是 並且 span 獨立

P2606 [ZJOI2010]排列計數

因為每個結點至多有一個前驅,所以我們可以發現這是一個二叉樹。現在我們要求的就是以1為根的二叉樹中,有多少種情況,滿足小根堆的性質。
\(f(i)\)表示以\(i\)為根的子樹中滿足小根堆性質的情況,那麽就有:\(f(i)=f(ls)*f(rs)*C_{sum(i)-1}^{sum(ls)}\)。表示選出\(sum(ls)\)個結點來作為左兒子中的結點,並且左右兒子都滿足小根堆的性質。這裏左右兒子這兩個問題都是獨立的,所以可以直接運用乘法原理。
這裏求組合數可以直接用Lucas定理來求,Lucas定理為:若p是一個質數,那麽\(C_n^m=C_{\frac{n}{p}}^{\frac{m}{p}}*C_{n\mod p}^{m\mod p}\mod p\)

代碼如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int N = 2e6 + 5;
ll n, p;
ll inv[N], fac[N], s[N], f[N];
ll C(ll a, ll b) {
    if(a < b) return 0;
    if(a == b || b == 0) return 1;
    if(a < p && b < p) return inv[b] * inv[a - b] % p * fac[a] % p;
    return C(a % p, b % p) * C(a / p, b / p) % p;
}
ll qp(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans ;
}
int main() {
    cin >> n >> p;
    fac[0] = 1;
    for(int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % p;
    for(int i = 1; i <= n; i++) inv[i] = qp(fac[i], p - 2) ;
    for(int i = 1; i <= n; i++) s[i] = 1;
    for(int i = n; i >= 2; i--) s[i >> 1] += s[i] ;
    for(int i = n; i >= 1; i--) {
        int ls = i << 1, rs = i << 1 | 1;
        if(f[ls] && f[rs]) f[i] = f[ls] * f[rs] % p * C(s[i] - 1, s[ls]) % p;
        else if(f[ls]) f[i] = f[ls] ;
        else f[i] = 1;
    }
    cout << f[1] ;
    return 0;
}

P2606 [ZJOI2010]排列計數