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