bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)相关的知识,希望对你有一定的参考价值。
【题目链接】
http://www.lydsy.com/JudgeOnline/problem.php?id=2111
【题意】
给定n,问1..n的排列中有多少个可以构成小根堆。
【思路】
设f[i]为以i为根的方案数,设l为左子树大小r为右子树大小,则有:
f[i]=C(i-1,l)*f[l]*f[r]
因为是个堆,所以子树大小都是确定的,可以直接递推得到。
其中C(n,m) nm比较大,可以用lucas定理求。
模型建立的重要性可知一二。。。
【代码】
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 5 typedef long long ll; 6 const int N = 5e6+10; 7 8 int mod,n; 9 ll f[N],fac[N],s[N]; 10 11 ll pow(ll a,ll p,int mod) 12 { 13 ll ans=1; 14 while(p) { 15 if(p&1) ans=(ans*a)%mod; 16 a=(a*a)%mod; p>>=1; 17 } 18 return ans; 19 } 20 21 void get_pre(int n) 22 { 23 fac[0]=1; 24 for(int i=1;i<=n;i++) 25 fac[i]=(fac[i-1]*i)%mod; 26 } 27 ll C(ll n,ll m,int mod) 28 { 29 if(n<m) return 0; 30 if(n<mod&&m<mod) { 31 ll invn=pow(fac[n-m],mod-2,mod); 32 ll invm=pow(fac[m],mod-2,mod); 33 return fac[n]*invm%mod*invn%mod; 34 } 35 return C(n/mod,m/mod,mod)*C(n%mod,m%mod,mod)%mod; 36 } 37 38 int main() 39 { 40 scanf("%d%d",&n,&mod); 41 get_pre(min(n,mod)); 42 for(int i=n;i;i--) { 43 s[i]=s[i<<1]+s[i<<1|1]+1; 44 f[i]=C(s[i]-1,s[i<<1],mod); 45 if((i<<1)<=n) f[i]=(f[i]*f[i<<1])%mod; 46 if((i<<1|1)<=n) f[i]=(f[i]*f[i<<1|1])%mod; 47 } 48 printf("%lld\n",f[1]); 49 return 0; 50 }
以上是关于bzoj 2111 [ZJOI2010]Perm 排列计数(DP+lucas定理)的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ2111[ZJOI2010]Perm 排列计数 组合数
bzoj 2111: [ZJOI2010]Perm 排列计数 (dp+卢卡斯定理)
组合数学+lucas定理+逆元 BZOJ2111 [ZJOI2010]Perm 排列计数