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 }

 

转载于:https://www.cnblogs.com/Leohh/p/8546860.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值