2111: [ZJOI2010]Perm 排列计数
2111: [ZJOI2010]Perm 排列计数
题意:
称一个1,2,...,N的排列$P_1,P_2...,P_n$是Magic的,当且仅当$2<=i<=N$时,$P_i>P_{i/2}$. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值
虽然是中文题,但是想加上markdown。
思路:
好题!
lucas定理+dp。
题目要求大小为n的小根堆的方案数,(即给定的二叉树的父节点大于子节点)。
f[i] 表示以i为根的子树的方案数。siz[i]为大小,即所有的取值。(假设这个子树的取值是1~siz[i])
f[i] = f[i*2] * f[i*2+1] * C(siz[i]-1,siz[i*2])。
f[i*2],f[i*2+1]是左右子树中的取值为1~siz的方案数,所以如果随机给这个子树siz个不同的数,同样是一组合法的方案。(给定的siz个数,可以映射到1~n上)。
所以总方案数就是,从所有的数中,选siz[ls]个,给左子树的方案数(剩下的自然就是给右子树)。首先根一定是1,在剩下的所有数中(siz[i]-1),给左孩子siz[i*2]个数。就是后面的式子。
问题:代码19行加入后,wa?
代码:
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 5 using namespace std; 6 typedef long long LL; 7 const int N = 2000100; 8 9 LL f[N],inv[N],dp[N],siz[N],t[N],p; 10 int n,mx; 11 12 void init() { 13 f[0] = f[1] = inv[0] = inv[1] = t[0] = t[1] = 1; 14 for (int i=2; i<=mx; ++i) { 15 f[i] = (f[i-1] * i) % p; 16 inv[i] = (-(p/i)*inv[p%i]) % p; 17 inv[i] = (inv[i] + p) % p; 18 t[i] = t[i-1] * inv[i] % p; 19 // if (inv[i] * i % p != 1) cout << 'a'; 20 } 21 } 22 LL Lucas(LL a,LL b) { 23 if (a < b) return 0; 24 if (a < p && b < p) 25 return f[a]*t[b]%p*t[a-b]%p; 26 return Lucas(a/p,b/p)*Lucas(a%p,b%p)%p; 27 } 28 int main() { 29 cin >> n >> p; 30 mx = min(LL(n),p); 31 init(); 32 for (int i=n; i>=1; --i) { 33 siz[i] = siz[i<<1] + siz[i<<1|1] + 1; 34 dp[i] = Lucas(siz[i]-1,siz[i<<1]); 35 if ((i<<1)<=n) dp[i] = (dp[i] * dp[i<<1]) % p; 36 if ((i<<1|1)<=n) dp[i] = (dp[i] * dp[i<<1|1]) % p; 37 } 38 cout << dp[1]; 39 return 0; 40 }