[ZJOI2010]排列计数
坐标\(/2\)这种神奇的性质肯定在暗示什么
基本是完全二叉树了
又发现\(P_{i}<P_{i/2}\),于是发现这是一棵小根堆
题意变成了求满足条件的小根堆有多少个
考虑一个树形\(dp\)
设\(dp_{i}\)表示\(i\)为根的小根堆有几种,\(sz_{i}\)表示这个小根堆的大小,假设当前小根堆就表示\(1\)到\(sz[i]\)这些数
那么\(i\)这个节点肯定要填上\(1\)
剩下的节点来自左右子树,我们分配一下,就是
\[dp_{ls}\times dp_{rs}\times \binom{sz_i-1}{sz_{ls}}
\]
就是先给左子树找好位置,之后剩下的插空填上就好了
可能需要用\(Lucas\)
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
#define re register
#define maxn 1000005
inline int read() {
int x=0;char c=getchar();while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int n;LL mod;
LL fac[maxn],inv[maxn],dp[maxn];
int sz[maxn];
inline LL C(int n,int m) {if(m>n) return 0;return fac[n]*inv[n-m]%mod*inv[m]%mod;}
inline LL ksm(LL a,int b) {LL S=1;while(b) {if(b&1) S=S*a%mod;b>>=1;a=a*a%mod;}return S;}
namespace sub1 {
inline void solve() {
fac[0]=1;for(re int i=1;i<=n;i++) fac[i]=(fac[i-1]*(LL)i)%mod;
inv[n]=ksm(fac[n],mod-2);
for(re int i=n-1;i>=0;--i) inv[i]=(inv[i+1]*(LL)(i+1))%mod;
for(re int i=1;i<=n;i++) sz[i]=1;
for(re int i=1;i<=n+1;i++) dp[i]=1;
for(re int i=n;i>=1;--i) {
sz[i>>1]+=sz[i];
if(sz[i]==1) continue;
dp[i]=C(sz[i<<1]+sz[i<<1|1],sz[i<<1])*dp[i<<1|1]%mod*dp[i<<1]%mod;
}
printf("%lld\n",dp[1]);
}
}
namespace sub2 {
inline LL Lucas(int n,int m) {
if(n<mod||m<mod) return C(n,m);
return Lucas(n/mod,m/mod)*C(n%mod,m%mod)%mod;
}
inline void solve() {
fac[0]=1;for(re int i=1;i<mod;i++) fac[i]=(fac[i-1]*(LL)i)%mod;
inv[mod-1]=ksm(fac[mod-1],mod-2);
for(re int i=mod-2;i>=0;--i) inv[i]=(inv[i+1]*(LL)(i+1))%mod;
for(re int i=1;i<=n;i++) sz[i]=1;
for(re int i=1;i<=n+1;i++) dp[i]=1;
for(re int i=n;i>=1;--i) {
sz[i>>1]+=sz[i];
if(sz[i]==1) continue;
dp[i]=Lucas(sz[i<<1]+sz[i<<1|1],sz[i<<1])*dp[i<<1|1]%mod*dp[i<<1]%mod;
}
printf("%lld\n",dp[1]);
}
}
int main() {
n=read(),mod=read();
if(mod>n) sub1::solve();
else sub2::solve();
return 0;
}