3805. 【NOIP2014模拟8.24】小X 的二叉堆计数

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

第一眼还以为是斯特林数,但仔细一看其实不是。它的堆的形态已经确定,所以我们很容易可以得到递推式f[i]=\binom{i-1}{li}*f[li]*f[ri](其中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;
}
发布了199 篇原创文章 · 获赞 201 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zsjzliziyang/article/details/103963392