参考资料
练习题 icon lost
交流讨论
笔记
img lost

我们知道包含 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
资源约定: 峰值内存消耗(含虚拟机) < 256M CPU 消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。 不要使用 package 语句。不要使用 jdk1.7 及以上版本的特性。
主类的名字必须是:Main,否则按无效代码处理。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000;
ll s[maxn+10],dp[maxn+10],inv[maxn+10],f[maxn+10];
ll n;
ll mod = 1000000009;
ll qPow(ll a,ll b)
{
    ll ans = 1;
    while(b!=0){
        if(b&1){
            ans = ans*a%mod;
        }
        a = a*a%mod;
        b >>= 1;
    }
    return ans%mod;
}
ll C(ll n,ll m)
{
    return (f[n]*inv[f[m]])%mod*inv[f[n-m]]%mod;
}
int main()
{
    cin >> n;
    f[0]=1;
    for(int i=1;i<=maxn;i++){
        f[i] = (f[i-1]*i)%mod;
        inv[i] = qPow(i,mod-2);
    }
    for(int i=n;i>=1;i--){
        s[i] = (s[i*2+1]<=n?s[i*2+1]:0) + (s[i*2]<=n?s[i*2]:0) + 1;
    }
    for(int i=1;i<=n;i++){
        dp[i]=1;
    }
    for(int i=n;i>=1;i--){
        if(i*2+1<=n){
            dp[i]=(C(s[i]-1,s[i*2+1])*dp[i*2+1])%mod*dp[i*2]%mod;
        }
    }
    cout << dp[1] << endl;
    return 0;
}

学习的这位大佬的思路:

思路:假设d[i]是以完全二叉树i号位置为根结点的二叉子堆个数,则考虑我们现在需要把n个点放入这个完全二叉树里,显然根节点已经被确定,只能放最小的,然后假设左子树的节点个数为lsize,则我们需要从n-1个节点中选出lsize个节点放入左子树,选法一共组合数C(n-1,lsize)种,剩余的放在右子树中,所以d[i]=C(n-1,lsize)*d[i的左儿子]*d[i的右儿子];

注意:求组合数需要用快速幂,乘法逆元的知识。以i为根节点个数可以先用动态规划算出来,s[i]=s[i的左儿子]+s[i的右儿子];

求阶乘逆元O(nlongn),动态规划O(n);

所以此算法的时间复杂度O(nlongn),在本题最大数据10^5下,具有时间可行性;
作者:heyAmos
来源:CSDN
原文:https://blog.csdn.net/yinxiaobao97/article/details/84881317
版权声明:本文为博主原创文章,转载请附上博文链接!

资料来源 蓝桥杯-堆的计数
博客作者 w1304636468
前往答题
我的笔记