zju1062/pku1095解题报告

 

题目分析:

要想构造出一棵编号为N的二*树,我们只需要知道左子树的编号和右子树的编号,就可以进行递归构造了。我们目前所能得到的额外的有用信息,就是具有n个结点的不同形态二*树的总数目Bn = C(2n, n) / (n + 1),但是这个公式的计算较为复杂。我们采用下面的方法来计算:
while(1)//f[t]是结点树目为 t 时二叉树的数目 
     {
         t++;
         for(i=0;i<=t-1;i++)
          f[t]+=f[i]*f[t-1-i];//f[i]为右子树的个数  f[t-1-i]为左子树的个树
         s+=f[i];
        if(s>500000000)
        break; 
     }   
用递推的方法来计算,n个结点的二叉树目=右子树的个数*左子树的个树,编程起来较为容易实现。


我们考虑采用逼近的思想。首先,计算出编号为n的二*树有几个结点:将n依次减去f[1],f[2],...,f[i],直到不能减为止,即此时n<f[i],设则此二*树共有t个结点,除去根结点,则左右子树共有t-1个结点。然后,计算左右子树各有几个结点:将n依次减去f[i]*f[t-1-i],i=t-1,t-2,...0,直到不能减为止,则左子树有t-1-i个结点,右子树有i 个结点。最后,计算左子树是具有t-1-i个结点的二*树中的第x棵,右子树是具有i个结点的二*树中的第y棵:将n整除f[i], x=m/f[i];
                                            if(m%f[i]!=0) x++;。
                                            y=(m-1)%f[i]+1;

(上面的公式只要对照例图看就可以得出,不过我还是想了很长的时间(注意是先排右子树,再排左子树的))


因此,我们求可得左子树的编号和右子树的编号,递归求解即可。f[n]可以在程序开始前用递推的方法高效地预先计算好,以后直接调用即可。

这道题用的逼近的思想,是一种非常重要的思想。对于一些全局统计的问题,如果可以进行局部统计,则可以利用局部统计的结果,逐步逼近全局统计,在逼近的过程中就可以计算出一些中间变量,利用它们便可以解决问题。

例程:

#include<iostream>
#include<string>
using namespace std;
long f[30];
long n;
void init()//递推计算结点为T时二叉树的数目 
 {
     memset(f,0,sizeof(f));
     int i,t;
     long s;
     s=t=0;
     f[0]=1;
     while(1)//结点为T时二叉树的数目 
     {
         t++;
         for(i=0;i<=t-1;i++)
          f[t]+=f[i]*f[t-1-i];
         s+=f[i];
        if(s>500000000)
        break; 
     }   
    
 }
void build(int t,long m)
 {
     int i;
     long k;
     i=t-1;
     //i为右子树的结点个数,t-1-i为左子树的个数
     while(m>f[i]*f[t-1-i])//f[i]为右子树的个数  f[t-1-i]为左子树的个树
       {
           m=m-f[i]*f[t-1-i];
           i--;
       }
     if(t-1-i>0)  //有左子树   
       {
           cout<<"(";
           k=m/f[i];//计算左子树是具有t-1-i个结点的二*树中的第k棵
           if(m%f[i]!=0) k++;
           build(t-1-i,k);
           cout<<")";
                                
       } 
      cout<<"X";
      if(i>0)
       {
           cout<<"(";  //右子树是具有i个结点的二*树中的第k棵
           k=(m-1)%f[i]+1;
           build(i,k);
           cout<<")";
       }      
 }      
 int main()
  {   

    int i;
      init();
      while(cin>>n&&n!=0)
        {
            i=1;
            while(n>f[i])//计算出编号为n的二*树有几个结点
              {
                  n=n-f[i];
                  i++;
              } 
            build(i,n);
            cout<<endl;   
           
        }   
     
      return 0;
  }  

 

另一个:

#include <iostream.h>

void makeTree(int n)
{
 int i, j, sum, m;
 int a[19] = {0, 1, 3, 8, 22, 64, 196, 625, 2055, 6917, 23713, 82499, 290511, 1033411,
     3707851, 13402696, 48760366, 178405156, 656043856};
 if(n == 0);
 else if(n == 1)
 {
  cout << 'X';
 }
 else if(n == 2)
 {
  cout << "X(X)";
 }
 else if(n == 3)
 {
  cout << "(X)X";
 }
 else if(n == 4)
 {
  cout << "X(X(X))";
 }
 else if(n == 5)
 {
  cout << "X((X)X)";
 }
 else if(n == 6)
 {
  cout << "(X)X(X)";
 }
 else if(n == 7)
 {
  cout << "(X(X))X";
 }
 else if(n == 8)
 {
  cout << "((X)X)X";
 }
 else
 {
  for(i=2; i<19; ++i)
  {
   if(a[i] <n && a[i+1]>=n)
    m= i+1;
  }
  if(n <=(2*a[m-1]-a[m-2]))
  {
   cout<<"X(";
   makeTree(n-a[m-1]+a[m-2]);
   cout<<')';
  }
  else if((n>a[m]+a[m-2]-a[m-1]) && n<=a[m])
  {
   cout<<'(';
   makeTree(a[m-1]-a[m]+n);
   cout <<")X";
  }
  else
  {
   sum =2*a[m-1]-a[m-2];
   for(i=1; i<m; ++i)
   {
    if(n>sum+(a[i]-a[i-1])*(a[m-i-1]-a[m-i-2]))
    {
     sum +=(a[i]-a[i-1])*(a[m-i-1]-a[m-i-2]);
    }
    else
     break;
   }

   j=1;
   while((sum+j*(a[m-i-1]-a[m-i-2]))<n)
    j++;

   cout<<'(';
   makeTree(j+a[i-1]);
   cout << ")X(";
   makeTree(n-sum-(j-1)*(a[m-i-1]-a[m-i-2])+a[m-i-2]);
   cout <<")";
  }
 }
}

int main()
{
 int n;
 cin >> n;
 while(n != 0)
 {
  makeTree(n);
  cout << endl;
  cin >> n;
 }
 return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值