BZOJ 2111 [ZJOI2010]Perm 排列计数:Tree dp + Lucas定理
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2111
题意:
给定n,p,问你有多少个1到n的排列P,对于任意整数i∈[2,n]满足P[i]>P[i/2]。
保证p为质数,输出答案 mod p的值。(n <= 10^6, p <= 10^9)
题解:
对于每个i,分别向i*2和i*2+1连一条边。
可以发现,最终形成的是一棵以1为根节点的二叉树。
题目中P[i]>P[i/2]的条件,就变成了:P[fa]<P[son]
然后就可以dp了。
表示状态:
dp[i]表示对于i的子树来说,填入1到siz[i]这些数,并且满足条件的方案数。
找出答案:
ans = dp[1]
如何转移:
对于i的子树来说,显然节点i只能填1。
所以首先考虑的就是将2到siz[i]这些数分配给两个子树的方案数。
设l = i*2, r = i*2+1,则方案数显然为C(siz[i]-1, siz[l])。
所以dp[i] = C(siz[i]-1, siz[l]) * dp[l] * dp[r]
边界条件:
dp[leaf] = siz[leaf] = 1
因为dp转移中要求组合数:C(n,m) = fact[n] * inv(fact[m]) * inv(fact[n-m])
然而给定的p可能很小,以至于与要求逆元的数不互质。
所以要用到Lucas定理求组合数:C(n,m)%p = C(n%p,m%p) * lucas(n/p,m/p) % p
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #define MAX_N 1000005 5 #define int ll 6 7 using namespace std; 8 9 typedef long long ll; 10 11 int n,p; 12 int f[MAX_N]; 13 int dp[MAX_N]; 14 int siz[MAX_N]; 15 16 void cal_f() 17 { 18 f[0]=1; 19 for(int i=1;i<=n;i++) f[i]=f[i-1]*i%p; 20 } 21 22 void exgcd(int a,int b,int &x,int &y) 23 { 24 if(b==0) 25 { 26 x=1,y=0; 27 return; 28 } 29 exgcd(b,a%b,y,x); 30 y-=(a/b)*x; 31 } 32 33 int inv(int a) 34 { 35 int x,y; 36 exgcd(a,p,x,y); 37 return (x%p+p)%p; 38 } 39 40 int c(int n,int m) 41 { 42 if(n<m) return 0; 43 return f[n]*inv(f[m])%p*inv(f[n-m])%p; 44 } 45 46 int lucas(int n,int m) 47 { 48 if(m==0) return 1; 49 return c(n%p,m%p)*lucas(n/p,m/p)%p; 50 } 51 52 void dfs(int x) 53 { 54 dp[x]=siz[x]=1; 55 int l=(x<<1),r=((x<<1)|1); 56 if(l<=n) dfs(l),siz[x]+=siz[l],dp[x]=dp[x]*dp[l]%p; 57 if(r<=n) dfs(r),siz[x]+=siz[r],dp[x]=dp[x]*dp[r]%p; 58 if(l<=n) dp[x]=dp[x]*lucas(siz[x]-1,siz[l])%p; 59 } 60 61 signed main() 62 { 63 cin>>n>>p; 64 cal_f(); 65 dfs(1); 66 cout<<dp[1]<<endl; 67 }