3172. 堆的计数

题目链接

3172. 堆的计数

我们知道包含 \(N\) 个元素的堆可以看成是一棵包含 \(N\) 个节点的完全二叉树。

每个节点有一个权值。

对于小根堆来说,父节点的权值一定小于其子节点的权值。

假设 \(N\) 个节点的权值分别是 \(1∼N\),你能求出一共有多少种不同的小根堆吗?

例如对于 \(N=4\) 有如下 \(3\) 种:

    1
   / \
  2   3
 /
4

    1
   / \
  3   2
 /
4

    1
   / \
  2   4
 /
3

由于数量可能超过整型范围,你只需要输出结果除以 \(1000000009\) 的余数。

输入格式

一个整数 \(N\)

输出格式

一个整数表示答案。

数据范围

对于 \(40\%\) 的数据,\(1≤N≤1000\)
对于 \(70\%\) 的数据,\(1≤N≤10000\)
对于 \(100\%\) 的数据,\(1≤N≤100000\)

输入样例:

4

输出样例:

3

解题思路

组合计数,dp

  • 状态表示:\(f[fa]\) 表示以 \(fa\) 为根节点的子树的方案数

  • 状态计算:\(f[fa]=C_{s[fa]-1}^{s[l]}\times f[l]\times f[r]\),其中 \(s[fa]\) 表示以 \(fa\) 为根节点的子树大小

先固定根节点,由于左右子树之间的方案数互不影响,故两者可利用乘法原理,除去根节点,左右子树共包含 \(s[fa]-1\) 个权值,将 \(s[l]\) 个权值分配给左子树即可,即有 \(C_{s[fa]-1}^{s[l]}\) 种分配方案

  • 时间复杂度:\(O(nlogn)\)

代码

#include <bits/stdc++.h>
#include <tr1/unordered_set>
#include <tr1/unordered_map>
#define pb push_back
#define fi first
#define se second
using namespace std;
using namespace std::tr1;

typedef pair<int,int> PII;
typedef long long LL;
typedef pair<LL,LL> PLL;

template <typename T>
inline void read(T &x)
{
	x=0;
	int f=1;char s=getchar();
	while(s<'0'||s>'9')f=-1,s=getchar();
	while(s>'0'&&s<='9')x=x*10+s-'0',s=getchar();
	
	x*=f;	
}

const int N=1e5+5,mod=1000000009;
int f[N],n,s[N],fact[N],inv_fact[N];
int ksm(int a,int b,int p)
{
	int res=1%p;
	while(b)
	{
		if(b&1)res=1ll*res*a%p;
		a=1ll*a*a%p;
		b>>=1;
	} 
	return res;
} 
void init()
{
	fact[0]=inv_fact[0]=1;
	for(int i=1;i<=n;i++)fact[i]=1ll*fact[i-1]*i%mod,inv_fact[i]=ksm(fact[i],mod-2,mod);
}
int C(int a,int b)
{
	return 1ll*fact[a]*inv_fact[b]%mod*inv_fact[a-b]%mod;
}
int main()
{
	cin>>n;
	init();
	for(int i=n;i;i--)
	{
		s[i]=1;
		int l=2*i,r=2*i+1;
		if(l<=n)s[i]+=s[l];
		if(r<=n)s[i]+=s[r]; 
	}
	for(int i=n/2+1;i<=n;i++)f[i]=1;
	for(int i=n/2;i;i--)
	{
		int l=2*i,r=2*i+1;
		if(l<=n)f[i]=C(s[i]-1,s[l]);
		if(l<=n)f[i]=1ll*f[i]*f[l]%mod;
		if(r<=n)f[i]=1ll*f[i]*f[r]%mod;
	}
	cout<<f[1];
	return 0;
}
posted @ 2022-04-05 10:57  zyy2001  阅读(111)  评论(0编辑  收藏  举报