1. 程式人生 > >洛谷P2606 [ZJOI2010]排列計數(數位dp)

洛谷P2606 [ZJOI2010]排列計數(數位dp)

void long bad oid tdi con 輸入 -m reg

題目描述

稱一個1,2,...,N的排列P1,P2...,Pn是Magic的,當且僅當2<=i<=N時,Pi>Pi/2. 計算1,2,...N的排列中有多少是Magic的,答案可能很大,只能輸出模P以後的值

輸入輸出格式

輸入格式:

輸入文件的第一行包含兩個整數 n和p,含義如上所述。

輸出格式:

輸出文件中僅包含一個整數,表示計算1,2,?, ???的排列中, Magic排列的個數模 p的值。

輸入輸出樣例

輸入樣例#1: 復制
20 23 
輸出樣例#1: 復制
16

說明

100%的數據中,1 ≤N ≤ 10^6, P≤ 10^9,p是一個質數。

題解

 數位dp?這怕不是個樹位dp……

  我們把原序列看成一棵二叉樹

  那麽就是要我們求大小為$n$的小根堆有多少個(就是父節點比左右兒子都小)

  那麽考慮dp,設$dp[i]$表示有多少個大小為$i$的小根堆,$val[i]$表示$i$的子樹的大小

  因為父親必須小於兒子,所以根節點只能是最小的點,那麽剩下的$i-1$個點裏有$val[l]$個可以放在左子樹,剩下的都可以放在右子樹,方案數為$C_{i-1}^{val[l]}$

  然後因為選不同的點之後還能有不同的方案,所以還要乘上方案數

  所以最後的狀態轉移方程是這樣的$dp[i]=C_{i-1}^{val[l]}*dp[val[l]]*dp[val[r]]$

  然後因為要組合數取模,得用上Lucas定理

 1 //minamoto
 2 #include<cstdio>
 3 #define ll long long
 4 const int N=1e6+5;
 5 ll inv[N],fac[N],val[N],dp[N],n,mod;
 6 #define min(a,b) ((a)<(b)?(a):(b))
 7 ll qpow(ll x,ll y){
 8     ll res=1;
 9     while(y){
10         if(y&1) res=res*x%mod;
11
y>>=1,x=x*x%mod; 12 } 13 return res; 14 } 15 void init(){ 16 int k=min(n,mod-1); 17 fac[0]=fac[1]=1; 18 for(int i=2;i<=k;++i) fac[i]=fac[i-1]*i%mod; 19 20 inv[k]=qpow(fac[k],mod-2); 21 for(int i=k-1;i;--i) inv[i]=(i+1)*inv[i+1]%mod; 22 } 23 ll C(ll n,ll m){ 24 if(m>n) return 0; 25 return fac[n]*inv[m]%mod*inv[n-m]%mod; 26 } 27 ll Lucas(ll n,ll m){ 28 if(m==0||m==n) return 1; 29 return Lucas(n/mod,m/mod)*C(n%mod,m%mod)%mod; 30 } 31 int main(){ 32 //freopen("testdata.in","r",stdin); 33 scanf("%lld%lld",&n,&mod);init(); 34 for(int i=n;i;--i){ 35 val[i]=1;if((i<<1)<=n) val[i]+=val[i<<1];if((i<<1|1)<=n) val[i]+=val[i<<1|1]; 36 if((i<<1|1)<=n) dp[i]=Lucas(val[i]-1,val[i<<1])*dp[i<<1]%mod*dp[i<<1|1]%mod; 37 else if((i<<1)<=n) dp[i]=dp[i<<1]; 38 else dp[i]=1; 39 } 40 printf("%lld\n",dp[1]); 41 return 0; 42 }

洛谷P2606 [ZJOI2010]排列計數(數位dp)