luogu2606 排列计数
题目大意
求满足下列条件的排列$P$的数量:$\forall P_i, P_i>P_{\lfloor \frac{i}{2}\rfloor}$。
思路
从下标入手
反过来想,也就是对$\forall P_i, P_i<P_{2i}且P_i<P_{2i+1}$。因为小根堆中一个“小三角”中节点的编号满足:若顶部编号为$i$,则左下角节点编号为"2i",右下角为$2i+1$,因此题目就是要让我们求大小为$n$的小根堆的数量。
递归式
因为堆这个结构有“子堆”这个子结构,所以可以递归。定义$l(n)$为大小为$n$的堆的左子堆大小,$f(i)$为大小为$i$,所有节点的值的取值范围一定(但并没有具体指定)时都不相等的堆有多少个。该堆的左子堆的个数等于当左子堆所有节点的值的取值范围的种数($C_{n-1}^{l(n)}$)乘以当所有节点的值的取值范围一定时的堆数($f(l(n))$)。分析完左子堆,随后还要乘以右子堆的堆数($f(r(n))$)。由于左子堆取值范围的种数确定了,右子堆的也确定了,所以不用再次乘以$C_{n-1}^{r(n)}$了。故总递归式为:
$$f(n)=C_{n-1}^{l(n)}f(l(n))f(r(n))$$
注意求组合数时要用Lucas定理取模。
怎么求$l(n),r(n)$?
注意这个没有通项公式,要按照堆的顺序递归解决。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define ll long long const int MAX_N = 1000010; ll F[MAX_N], Fact[MAX_N]; int Size_Lsize[MAX_N], Size_Rsize[MAX_N]; int GetSize(int curNode, int n) { if (curNode > n) return 0; int lSize = GetSize(curNode * 2, n), rSize = GetSize(curNode * 2 + 1, n), curSize = lSize + rSize + 1; Size_Lsize[curSize] = lSize, Size_Rsize[curSize] = rSize; return curSize; } void GetFact(int n, int p) { Fact[0] = Fact[1] = 1; for (ll i = 2; i <= n; i++) Fact[i] = i * Fact[i - 1] % p; } ll Mult(ll a, ll b, ll p) { ll ans = 0; while (b) { if (b & 1) ans = (ans + a) % p; a = (a + a) % p; b >>= 1; } return ans; } ll Power(ll a, ll n, ll p) { ll ans = 1; while (n) { if (n & 1) ans = Mult(ans, a, p); a = Mult(a, a, p); n >>= 1; } return ans; } ll Inv(ll a, ll p) { return Power(a, p - 2, p); } ll Comb(int n, int m, int p) { return Fact[n] * Inv(Mult(Fact[n - m], Fact[m], p), p); } ll Lucas(int n, int m, int p) { if (m == 0) return 1; return Comb(n % p, m % p, p) * Lucas(n / p, m / p, p) % p; } ll Dfs(int n, int p) { if (F[n]) return F[n]; if (n == 1 || n == 0) return F[n] = 1; return F[n] = Dfs(Size_Lsize[n], p) * Dfs(Size_Rsize[n], p) % p * Lucas(n - 1, Size_Lsize[n], p) % p;//易忘点:Dfs后的%p } int main() { int n; ll p; scanf("%d%lld", &n, &p); GetSize(1, n); GetFact(n, p); printf("%lld\n", Dfs(n, p)); return 0; }