[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 }