题意:称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值。
解法:我们仔细观察这个pi>=pi/2,想到什么了?像不像二叉树中每个点i和它的两个儿子的编号2i和2i+1。
那么我们可以想象每个点i想它的两个儿子2i/2i+1连边,加上Pi>Pi/2这个条件,那么这棵二叉树就是一棵小根堆。那么我们考虑用dp解决这道题,
设dp[i]表示i个不同的数组成一棵大小为i的小根堆的方案数,状态转移方程为dp[i]=C(i-1,l[i]) * dp[l[i]] * dp[r[i]] ;解释一下:这里的l[i]/r[i]代表大小为i的完全二叉树(为什么要是完全的?因为题目要求的序号是连续的)的左/右子树大小。这个方程的意思是从i-1个数里面选择l[i]个数作为左子树方案数乘以剩下r[i]个数作为右子树方案数。
那么我们预处理l[i]/r[i]就可以计算答案了。
注意此题p有可能>=n,所以要用Lucas定理计算组合数。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int N=1e6+10; 5 int n,P,l[N],r[N],dp[N]; 6 7 int power(int x,int p) { 8 int ret=1; 9 for (;p;p>>=1) { 10 if (p&1) ret=(LL)ret*x%P; 11 x=(LL)x*x%P; 12 } 13 return ret; 14 } 15 16 int fac[N],inv[N]; 17 void prework(int n) { 18 fac[0]=1; inv[0]=1; 19 for (int i=1;i<=n;i++) { 20 fac[i]=(LL)i*fac[i-1]%P; 21 inv[i]=power(fac[i],P-2); 22 } 23 l[1]=0; 24 for(int i=2,g=1;i<=n;g<<=1,i+=g) { 25 for(int j=1;j<=g;j++) l[i+j-1]=l[i+j-2]+1; 26 for(int j=1;j<=g;j++) l[i+g+j-1]=l[i+g+j-2]; 27 } 28 for (int i=1;i<=n;i++) r[i]=i-1-l[i]; 29 } 30 31 int C(int n,int m) { 32 if (n>=P || m>=P) return (LL)C(n/P,m/P)*C(n%P,m%P)%P; 33 else return (LL)fac[n]*inv[m]%P*inv[n-m]%P; 34 } 35 36 int main() 37 { 38 cin>>n>>P; 39 prework(n); 40 dp[0]=dp[1]=1; 41 for (int i=2;i<=n;i++) 42 dp[i]=(LL)C(i-1,l[i])*dp[l[i]]%P*dp[r[i]]%P; 43 cout<<dp[n]<<endl; 44 return 0; 45 }