題解 P2606 【[ZJOI2010]排列計數】
阿新 • • 發佈:2020-12-14
Solution [ZJOI2010]排列計數
題目大意:求 \(1-n\) 的排列 \(p\) 中,有多少個排列滿足 \(\forall i\in [2,n],p_i > p_{\lfloor\frac{i}{2}\rfloor}\),對給定質數 \(m\) 取模。
分析:
\(p_i > p_{\lfloor\frac{i}{2}\rfloor}\),反過來 \(\forall x,p_x<p_{2x},p_{2x+1}\),那麼問題變成一棵 \(n\) 個節點的完全二叉樹,將數 \(1-n\) 分配給每一個節點,使得每個父節點都比它兩個子節點權值要小的方案數。
進一步,我們不關心數究竟是啥,我們只關心它們之間的相對大小關係。
記 \(siz[u]\) 表示以 \(u\) 為根的子樹大小,\(f[u]\) 表示將 \(siz[u]\) 個互不相同的數分配給以 \(u\) 為根的子樹的合法方案數。記 \(ls,rs\) 為左右兒子。
那麼 \(f[u]=f[ls]\cdot f[rs] \cdot \binom{siz[ls]+siz[rs]}{siz[ls]}\)
坑點在於,\(n\) 有可能比 \(m\) 大,此時需要使用 \(\text{Lucas}\) 定理
#include <cstdio> using namespace std; const int maxn = 1e6 + 100; int n,mod; inline int add(const int a,const int b){return (a + b) % mod;} inline int mul(const int a,const int b){return (1ll * a * b) % mod;} inline int qpow(int base,int b){ int res = 1; while(b){ if(b & 1)res = mul(res,base); base = mul(base,base); b >>= 1; } return res; } inline int inv(const int x){return qpow(x,mod - 2);} int fac[maxn]; inline void init(){ fac[0] = 1; for(int i = 1;i <= n;i++)fac[i] = mul(fac[i - 1],i); } extern inline int C(const int n,const int m); inline int lucas(const int n,const int m){ if(n < mod)return C(n,m); return mul(C(n / mod,m / mod),C(n % mod,m % mod)); } inline int C(const int n,const int m){return n < mod ? mul(fac[n],inv(mul(fac[m],fac[n - m]))) : lucas(n,m);} int f[maxn << 2],siz[maxn << 2]; #define ls (u << 1) #define rs (u << 1 | 1) inline void dp(const int u){ f[u] = 1; if(u < 1 || u > n)return; siz[u] = 1; dp(ls); dp(rs); siz[u] += siz[ls]; siz[u] += siz[rs]; f[u] = mul( C(siz[u] - 1,siz[ls]), mul(f[ls],f[rs]) ); } #undef ls #undef rs int main(){ scanf("%d %d",&n,&mod); init(); dp(1); printf("%d\n",f[1]); return 0; }