[BZOJ2111][ZJOI2010]Perm排列计数(组合数学)

题意就是求一个n个点的堆的合法形态数。

显然,给定堆中所有数的集合,则这个堆的根是确定的,而由于堆是完全二叉树,所以每个点左右子树的大小也是确定的。

设以i为根的堆的形态数为F(i),所以F(i)+=F(sz[2*i])*F(sz[2*i+1])*C(sz[i]-1,sz[2*i])。直接DP即可。

有个令人无语的坑,n可能大于p,要用Lucas。

还有求阶乘逆元的时候根本不需要用快速幂算出fac[n]的逆元再逆推回去,直接跟阶乘一样顺推就好了。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 using namespace std;
 5 
 6 const int N=1000010;
 7 int n,p,fac[N],inv[N],Fin[N],s[N],f[N];
 8 
 9 int C(int n,int m){
10     if (n<m) return 0;
11     if (n<p && m<p) return 1ll*fac[n]*Fin[m]%p*Fin[n-m]%p;
12     return 1ll*C(n/p,m/p)*C(n%p,m%p)%p;
13 }
14 
15 int main(){
16     freopen("bzoj2111.in","r",stdin);
17     freopen("bzoj2111.out","w",stdout);
18     scanf("%d%d",&n,&p); int m=min(n,p);
19     fac[0]=1; rep(i,1,m) fac[i]=1ll*fac[i-1]*i%p;
20     inv[1]=1; rep(i,2,m) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
21     Fin[0]=1; rep(i,1,m) Fin[i]=1ll*Fin[i-1]*inv[i]%p;
22     for (int i=n; i; i--){
23         s[i]=s[i<<1]+s[(i<<1)|1]+1;
24         f[i]=1ll*((i<<1)>n?1:f[i<<1])*((i<<1|1)>n?1:f[i<<1|1])%p*C(s[i]-1,s[i<<1])%p;
25     }
26     printf("%d\n",f[1]);
27     return 0;
28 }

 

posted @ 2018-07-09 19:39  HocRiser  阅读(247)  评论(0编辑  收藏  举报