POJ—1095 Trees Made to Order(卡特兰数+递归)

题目链接

题意:

如图编号,结点数依次增加,每次先对左子树进行递归操作,再对右子树进行递归,用X表示一个节点,每一对括号表示一棵树,以 (左子树)X(右子树) 的形式输出这棵树,当然如果是空树就不输出, 求编号为n的二叉树

在这里插入图片描述

思路:

1.先还原二叉树形态,后递归
  • 1 首先二叉树的种类数满足卡特兰数,所以有递推关系可得出任意结点的卡特兰数ct[],根据编号找到它的结点数,和在该结点数下的第几棵树。

  • 2 由编号还原二叉树形态,右子树满足卡特兰数满状态时,左子树移动一下,直到左子树满足它的卡特兰数。

  • 3 递归画图,递归框架,从根节点开始画,第一层传参时,不输出左右括号,其余结点,只要存在就要有右括号,画完左边画’X’画右边

f(LL x,bool flag=1)
{
    if(flag)
    putchar('(');
    f();
    putchar('X');
    f();
    if(flag)
    putchar(')');
}

然后思路理好后贴代码,有些地方是挺难想明白,画个图就慢慢清楚了

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string.h>
#include<cstdlib>
#include<cstring>
#include<math.h>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#define LL long long
#define Pr pair<int,int>
using namespace std;
int n,m;
LL ct[233];
LL cts[233];
void init()
{
   ct[0]=1;
   cts[0]=0;
   for(int t=1;t<33;t++)
   {
       ct[t]=ct[t-1]*(4*t-2)/(t+1);
       cts[t]=cts[t-1]+ct[t-1];
   }
}
void f(LL x,bool flag=1)
{
    if(x==0)
        return ;
    LL l,r;
    int pos;
    for(int i=0;;i++)
    {
        if(x<cts[i])
        {
            pos=i-1;   //结点
            break;
        }
    }
    l=0;
    r=pos-1;
    x-=cts[pos];     //位于pos结点卡特兰数的第x位
    LL p1=0;
    while(x-ct[r]>=0)//还原二叉树的形态
    {
        x-=ct[r];
        p1++;        //左子树形态+1
        if(p1==ct[l])
        {
            l++;
            r--;
            p1=0;
        }
    }
    if(flag)
    putchar('(');
    f(cts[l]+p1);
    putchar('X');
    f(cts[r]+x);
    if(flag)
    putchar(')');
}
int main()
{
    init();
    LL x;
    while(scanf("%lld",&x)!=EOF)
    {
        if(x==0)break;
        f(x,0);
        cout<<endl;
    }

    return 0;
}
2.对左子树为空,右子树为空,左右子树非空递归
  • 1 一样推出这棵树有多少个节点,以及是当前第几个编号
  • 2 当编号小于ct[结点-1]时,左子树此时为空,往下递归,结点数减一,编号不变(如图结点为4时的第二棵,在结点为3时依旧为第二棵)
    在这里插入图片描述
  • 3 当编号大于ct[结点]-ct[结点-1],右子树为空,递归时所在编号需要减去(左子树为空+左右子树非空)的树的数目,即n-(ct[结点]-ct[结点-1])
    在这里插入图片描述
  • 4 左右子树均非空时,先推出右子树的节点数
//pos为当前结点数

 int t;
        for(int i=pos-1;i>=0;i--)       //例如pos为4,左右子树结点和为3,枚举i=3,2,1,i为右子树数目
        {
            if(n>ct[i]*ct[pos-1-i])     //有 ct[3]*ct[0]  ,  ct[2]*ct[1]  ,ct[1]*ct[2]  ,  ct[0]*ct[3]
                n-=ct[i]*ct[pos-1-i];   //假设取到第二段,右子树结点为2,左子树结点为1 ,n为在第二段中的编号
            else
            {
                t=i;//推出右子树的结点数
                break;
            }
        }
  • 5 分别递归左右子树
        printf("(");
        f(n/ct[t]+(n%ct[t]!=0),pos-1-t);//递归左子树,每进行一次右子树的ct[],左子树形态加1,别忘了后面非整除再+1
        printf(")X(");
        f((n-1)%ct[t]+1,t);//递归右子树,取余得到右子树形态,编号保证不为0
        printf(")");

在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string.h>
#include<cstdlib>
#include<cstring>
#include<math.h>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#define LL long long
#define Pr pair<int,int>
using namespace std;
int n,m;
long long ct[233];   //要long long 类型
void init()
{
   ct[0]=1;
   for(int t=1;t<33;t++)
   {
       ct[t]=ct[t-1]*(4*t-2)/(t+1);
   }
}
void f(int n,int pos)//有pos个结点,是其中的第n个编号
{
    if(pos==1)
    {
        printf("X");
        return ;
    }
    if(n<=ct[pos-1])//左子树为空树
    {
        printf("X(");
        f(n,pos-1);
        printf(")");
    }
    else if(n>ct[pos]-ct[pos-1])//右子树为空树
    {
        printf("(");
        f(n-(ct[pos]-ct[pos-1]),pos-1);
        printf(")X");

    }
    else //左右子树都是非空
    {
        int t;
        for(int i=pos-1;i>=0;i--)
        {
            if(n>ct[i]*ct[pos-1-i])
                n-=ct[i]*ct[pos-1-i];
            else
            {
                t=i;//推出右子树的结点数
                break;
            }
        }
        printf("(");
        f(n/ct[t]+(n%ct[t]!=0),pos-1-t);
        printf(")X(");
        f((n-1)%ct[t]+1,t);
        printf(")");
    }
}
int main()
{
    init();
    int x;
    while(scanf("%d",&x)!=EOF)
    {
        if(x==0)
            break;
        for(int i=1;i<33;i++)//推出这棵树的节点数
            if(x>ct[i])
                x-=ct[i];
            else
            {
                f(x,i);
                break;
            }
        cout<<endl;
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值