BZOJ 2111 [ZJOI2010]Perm 排列計數:Tree dp + Lucas定理

Leohh發表於2018-03-11

題目連結: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 }

 

相關文章