[ZJOI2010]排列計數 (組合計數/dp)
阿新 • • 發佈:2018-10-12
數據 限制 getchar() 計算 由於 文件中 pre 模擬 inline
... 沒想到啊...
[ZJOI2010]排列計數
題目描述
稱一個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是一個質數。
Solution
組合計數+dp
因為這道題標簽有數位dp...exm?
好了回到這道題,看到題目中關鍵的一個式子
\[p_i>p_{i/2}\]
有沒有想到線段樹中子節點與父節點編號的關系,其實這就是一個小根堆的限制,我們要找出所有的小根堆的個數
可以\(dp\)求出來,對於一個節點i,包括它自己有\(size\)個節點,那麽我們考慮除它自己以外\(size-1\)個節點中有\(l\)個節點可以作為左子樹,剩下的作為右子樹
\[dp[i]=C_{size[i]-1}^{size[l]}\times dp[l]\times dp[r]\]
至於\(size[l]怎麽求?模擬dfs自下而上更新就好\)
然後,由於模數p可能<n(可能出現n%p==0,那就求不出逆元),所以要用lucas求
Code
#include<bits/stdc++.h> #define rg register #define il inline #define Min(a,b) (a)<(b)?(a):(b) #define Max(a,b) (a)>(b)?(a):(b) #define lol long long #define ll(x) (x<<1) #define rr(x) (x<<1|1) #define in(i) (i=read()) using namespace std; const int N=1e6+10; lol read() { lol ans=0,f=1; char i=getchar(); while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();} while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0',i=getchar(); return ans*=f; } lol n,mod; lol sum[N]={1},inv[N]={1},dp[N],size[N<<1]; lol qpow(lol a,lol x,lol ans=1) { while(x) { if(x&1) ans=ans*a%mod; x>>=1,a=a*a%mod; }return ans; } void init() { for(lol i=1;i<=n;i++) sum[i]=sum[i-1]*i%mod; for(lol i=1;i<=n;i++) inv[i]=qpow(sum[i],mod-2); } lol C(lol n,lol m) { if(m>n) return 0; return sum[n]*inv[m]%mod*inv[n-m]%mod; } lol Lucas(lol n,lol m) { if(!m) return 1; return C(n%mod,m%mod)*Lucas(n/mod,m/mod)%mod; } int main() { in(n),in(mod); init(); for(int i=n;i>=1;i--) { size[i]=size[ll(i)]+size[rr(i)]+1; dp[i]=Lucas(size[i]-1,size[ll(i)])*(ll(i)>n?1:dp[ll(i)])%mod*(rr(i)>n?1:dp[rr(i)])%mod; } cout<<dp[1]<<endl; }
[ZJOI2010]排列計數 (組合計數/dp)