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

64 篇文章 0 订阅
5 篇文章 0 订阅

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值