HDU OJ -- How Many Trees?

8 篇文章 1 订阅

一、问题描述

1.概述

二元搜索树是一种树形结构,树中每个节点最多具有两个分支并且带有一个权值。

对于某一节点x,任何可以从其左侧到达的节点y都具有性质:y的权值<x的权值;

任何可以从其右侧到达的节点z都具有性质:z的权值>x的权值。

现问给出总节点数n,总共能构造出多少种不同形状的二元搜索树?

2.题目链接

HDU OJ -- How Many Trees?

3.题目截图

如图1.1所示。


图1.1  题目截图

二、算法思路

下面将分别以n=1,2,3,4时的情况说明算法的思路。

1.当n=1时,以a表示仅有的节点

很明显,一个节点时只能构成唯一的一种以a为根节点的树形结构,如图2.1所示。


图2.1  当n=1时的树形结构

2.当n=2时,以(a,b)表示节点列表,并且设权值关系为:a<b

注意:n>1时的树形结构,就是以不同的节点作为根节点时的树形结构的总和。

1)a作为根节点

当a作为根节点时,由于a具有最小的权值,因此剩下的所有节点只可能在a的右侧分布。由于只剩下一个节点b,并且在前边已经算出了在只有一个节点时(n=1时)总共能构造出1种树形结构,因此在此情况下,只能构造出1种树形结构。如图2.2所示。


图2.2  n=2时当a作为根节点的树形结构

2)b作为根节点

由于b具有最大的权值,因此剩下的节点将在b的左侧分布。因为剩下一个节点a,所以此情况下也只能构造出1种树型结构。如图2.3所示。


图2.3  n=2时当b作为根节点的树形结构

3.当n=3时,以(a,b,c)表示节点列表,并且设权值关系为:a<b<c

1)a作为根节点

a是权值最小的节点,剩下的节点将在a的右侧分布。由于剩下两个节点:b、c,并且在上述已经说明了当有两个节点(n=2时)总共可以构造出2种树形结构,因此在这情况下总共有2种树形结构。如图2.4所示。


图2.4  n=3时当a作为根节点的树形结构

2)b作为根节点

b不是权值最值端(最大权值或最小权值)的节点,这意味着剩下的节点将在b的左右两侧分布(a分布在左侧,b分布在右侧)。如图2.5所示,因为左右两侧只有一个节点,并且一个节点时所具有的树形结构数目已知,所以这种情况下具有的树形结构总和为:1(左侧:n=1) x 1(右侧:n=1) = 1。


图2.5  n=3时当b作为根节点的树形结构

3)c作为根节点

c是权值最大的节点,剩下的节点将在c的左侧分布。由于剩下两个节点:a、b,因此在这情况下总共有2种树形结构。如图2.6所示。


图2.6  n=3时当c作为根节点的树形结构

4.当n=4时,以(a,b,c,d)表示节点列表,并且设权值关系为:a<b<c<d

1)a作为根节点

a为权值最小的节点,此情况下的树形结构将是剩下的三个节点具有的树形结构,如图2.7所示。三个节点的情况已经由上面求出。


图2.7  n=4时当a作为根节点的树形结构

2)b作为根节点

当b为根节点时,在剩下的节点中,a将分布在b的左侧;c、d将分布在b的右侧,如图2.8所示。

此时具有的树形结构 = 左侧具有的树形结构 x 右侧具有的树形结构:1(左侧n=1) x 2(右侧n=2) = 2。


图2.8  n=4时当b作为根节点的树形结构

3)c作为根节点

与b作为根节点类似,剩下的节点中,a、b分布在c的左侧,d分布在c的右侧,如图2.9所示。


图2.9  n=4时当c作为根节点的树形结构

4)d作为根节点

与a作为根节点类似,如图2.10所示。


图2.10  n=4时当d作为根节点的树形结构


5.对上面的归纳

经过n的四种情况的描述,可以发现问题具有两个特点。

1)对称性

当n是偶数时,如n=2时(节点序列为a、b,且a<b),树形结构数目(以a为根节点)=树形结构数目(以b为根节点)。

如n=4时(节点序列为a、b、c、d且a<b<c<d),树形结构数目(以a为根节点)=树形结构数目(以d为根节点);树形结构数目(以b为根节点)=树形结构数目(以c为根节点)。

这是因为,拿n=4的情况说明,当以a为根节点,由于其权值是最小的,将会有三个节点在其右侧自由组合,由于各节点权值不同,有最小的一定有最大的,在此d是最大的,当以d为根节点时,将会有三个节点在其左侧自由组合。

当以b为根节点时,由于b的权值是第二小的,意味着它的结果 = 左边一个比它小的节点的组合 x 右边两个比它大的节点的组合。

对应的c是第二大的,它的结果=左边的两个比它小的组合 x 右边的一个比它大的节点的组合。

当n是奇数时,首尾对应的节点也具有同样的规律,如n=3时(节点序列为a、b、c且a<b<c),树形结构数目(以a为根节点)=树形结构数目(以c为根节点)。中间节点b作为根节点时则需要单独计算,由于中间节点将剩余的节点数(n-1个节点)平均分配在其左右两侧,因此以中间节点为根节点时的树形结构数目 = 当n=(n-1)/2时的树形结构数目的平方

2)大规模问题的解决依赖于已知的小规模问题

从n的四种情况可以看出,除了n=1,其他情况下的结果都要依赖于之前计算出的小规模结果,如n=4时,以a为根节点时,需要利用n=3的结果;如n=4时,以b为根节点,需要利用n=1和n=2的结果。

这种情况下用数组来保存已计算出的结果将会变得非常便利。

三、算法实现

当n很大时,结果将超过long long类型的范围,因此此题应用字符串表示结果的数字,在实现上,使用了一个类完成对字符串数字的加法和乘法。

#include <iostream>
using namespace std;
class NumStr
{
public:
    string str;
    NumStr()=default;
    NumStr(string str):str(str){}
    NumStr operator+(NumStr& oprand)
    {
        NumStr ret;
        bool carry=0;
        unsigned minLen=str.size()<=oprand.str.size()?str.size():oprand.str.size();
        char tmp;
        for(unsigned i=0;i<minLen;i++)
        {
            tmp=str[i]+oprand.str[i]+carry-'0';
            if(tmp>'9')
            {
                carry=1;
                tmp-=10;
            }
            else
            {
                carry=0;
            }
            ret.str+=tmp;
        }
        if(str.size()>minLen)
        {
            for(unsigned i=minLen;i<str.size();i++)
            {
                tmp=str[i]+carry;
                if(tmp>'9')
                {
                    carry=1;
                    tmp-=10;
                }
                else
                {
                    carry=0;
                }
                ret.str+=tmp;
            }
        }
        if(oprand.str.size()>minLen)
        {
            for(unsigned i=minLen;i<oprand.str.size();i++)
            {
                tmp=oprand.str[i]+carry;
                if(tmp>'9')
                {
                    carry=1;
                    tmp-=10;
                }
                else
                {
                    carry=0;
                }
                ret.str+=tmp;
            }
        }
        if(carry)
            ret.str+='1';
        return ret;
    }
    NumStr operator*(unsigned char oprand)
    {
        NumStr ret;
        unsigned char carry=0;
        unsigned char tmp;
        for(unsigned i=0;i<str.size();i++)
        {
            tmp=(str[i]-'0')*oprand+carry;
            ret.str+=(tmp%10+'0');
            carry=tmp/10;
        }
        if(carry!=0)
            ret.str+=(carry+'0');
        return ret;
    }
    NumStr operator*(NumStr& oprand)
    {
        NumStr ret,tmpStr;
        for(unsigned i=0;i<oprand.str.size();i++)
        {
            if(oprand.str[i]!='0')
            {
                tmpStr=*this*(oprand.str[i]-'0');
                if(i>0)
                    tmpStr.str.insert(0,i,'0');
                ret=ret+tmpStr;
            }
        }
        return ret;
    }
    void print()
    {
        for(int i=str.size()-1;i>=0;i--)
            cout<<str[i];
    }
};
NumStr f[101];
void preDeal()
{
    f[0]=f[1]=NumStr("1");
    f[2]=NumStr("2");
    f[3]=NumStr("5");
    unsigned short limit;
    for(unsigned i=4;i<=100;i++)
    {
        NumStr res,tmpStr;
        limit=i/2;
        for(unsigned j=0;j<limit;j++)
        {
            tmpStr=f[j]*f[i-1-j];
            res=res+tmpStr;
        }
        f[i]=res*2;
        if(i%2!=0)
        {
            tmpStr=f[(i-1)/2]*f[(i-1)/2];
            f[i]=f[i]+tmpStr;
        }
    }
}
int main()
{
    using namespace std;
    preDeal();
    unsigned in;
    while(cin>>in)
    {
        f[in].print();
        cout<<endl;
    }
//    NumStr a("51");
//    NumStr b=a*2;
//    a.print();
//    cout<<endl;
//    b.print();
//    cout<<endl;
//    b=b*a;
//    b.print();
//    cout<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值