组合数+Lucas定理
题目可以转化成求1~n排列的小根堆数目,那么对于每一个i位置,他的子树节点个数是确定的,记为f[i],那么有f[i]=C(siz[i-1],siz[i<<1])*f[i<<1]*f[i<<1|1]
注意到n可能大于p,套Lucas定理即可
【Lucas定理】注意,当且仅当p是质数时才可以用Lucas定理。在不考虑求逆元的情况下,单次时间复杂度 O(logpn)
#include<cstdio>
#define N 1000005
#define ll long long
using namespace std;
int n, p, siz[N<<1];
ll fac[N], inv[N], f[N<<1];
void init()
{
fac[0]=1;
for(int i = 1; i <= n && i < p; i++)
fac[i]=fac[i-1]*i%p;
inv[1]=1;
for(int i = 2; i <= n && i < p; i++)
inv[i]=-(p/i)*inv[p%i]%p;
inv[0]=1;
for(int i = 1; i <= n && i < p; i++)
inv[i]=inv[i]*inv[i-1]%p;
for(int i = n; i; i--)
siz[i]=siz[i<<1]+siz[i<<1|1]+1;
for(int i = n+1, ii = 2*N; i < ii; i++)
f[i]=1;
}
ll C(int m, int n)
{
if(m<n)return 0;
if(m<=p && n<=p)
{
return fac[m]*inv[n]%p*inv[m-n]%p;
}
else return C(m/p,n/p)*C(m%p,n%p)%p;
}
int main()
{
scanf("%d%d",&n,&p);
init();
for(int i = n; i; i--)
f[i]=f[i<<1]*f[i<<1|1]%p*C(siz[i]-1,siz[i<<1])%p;
printf("%lld\n",(f[1]+p)%p);
}