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 }

 

posted @ 2018-05-02 19:23  MJT12044  阅读(344)  评论(0编辑  收藏  举报