Description
众所周知,完全二叉树是一种二叉树,满足除最后一层外的每层结点都是满的,且最后一层的结点连续集中在左方。而二叉堆是一种完全二叉树,分为大根堆和小根堆,大根堆满足父结点的值不小于子结点的值,小根堆满足父结点的值不大于子结点的值。
小X 最近对二叉堆和树的计数都很感兴趣,他想知道n 个互不相同的数能构成多少个不同的大小为n 的二叉堆,希望你帮帮他。
Input
第一行包含一个整数n。
Output
第一行包含一个整数,表示能构成的二叉堆个数模10^9 + 7。
Sample Input
3
Sample Output
4
Data Constraint
对于30% 的数据,n ≤ 10。
对于60% 的数据,n ≤ 1000。
对于80% 的数据,n ≤ 10^5。
对于100% 的数据,1 ≤ n ≤ 5 × 10^6。
Solution
第一眼还以为是斯特林数,但仔细一看其实不是。它的堆的形态已经确定,所以我们很容易可以得到递推式(其中l[i]表示左子树的儿子个数,ri是是右子树)
表示第一个对顶元素已经确定后,任意选择li个数放到左子树,剩下的数放到右子树后,再乘上左子树和右子树的形态的方案数就是i的答案。
最后答案是f[n]*2,有大小根堆。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define I int
#define ll long long
#define F(i,a,b) for(I i=a;i<=b;i++)
#define Fd(i,a,b) for(I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof(a))
#define N 5000010
#define M 1000000007
using namespace std;
void rd(ll &x){
x=0;ll w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
x*=w;
}
ll n,T[24],S[24],f[N],F[N],inv[N];
ll ksm(ll x,ll k){
if(k==1) return x%M;
ll st=ksm(x,k/2);st=(st*st)%M;
if(k&1) return st*x%M;
return st;
}
ll work(ll x){
ll p=1,sum=0,left=0;
while(S[p+1]<=x) p++;
sum+=(S[p]-1)>>1;
left=x-S[p];
if(left>=T[p-1]) sum+=T[p-1];
else sum+=left;
return sum;
}
ll C(ll n,ll m){return f[n]*inv[m]%M*inv[n-m]%M;}
I main(){
rd(n);
if(n==1){
printf("1\n");
return 0;
}
T[0]=f[0]=F[0]=F[1]=1;
F(i,1,23) T[i]=T[i-1]*2;
F(i,1,23) S[i]=S[i-1]+T[i-1];
F(i,1,n) f[i]=(f[i-1]*(ll)i)%M;
inv[n]=ksm(f[n],M-2);
Fd(i,n-1,0) inv[i]=inv[i+1]*(ll)(i+1)%M;
F(i,2,n){
ll x=work(i);
F[i]=C(i-1,x)*F[x]%M*F[i-x-1]%M;
}
printf("%lld\n",(F[n]*2)%M);
return 0;
}