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