山东济南彤昌机械科技有限公司 山东济南江鹏工贸游有限公司

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 }

 

posted on 2016-04-03 17:59  hahalidaxin  阅读(763)  评论(0编辑  收藏  举报