[NOIP2004 普及组] FBI 树 队列解法

22 篇文章 1 订阅
20 篇文章 1 订阅

[NOIP2004 普及组] FBI 树

题目描述:

我们可以把由 0 和 1 组成的字符串分为三类:全 0 串称为 B 串,全 1 串称为 I 串,既含 0 又含 1 的串则称为 F 串。

FBI 树是一种二叉树,它的结点类型也包括 F 结点,B 结点和 I 结点三种。由一个长度为 $2^N$ 的 01 串 S 可以构造出一棵 FBI 树 T,递归的构造方法如下:

1. T 的根结点为 R,其类型与串 S 的类型相同;
2. 若串 S 的长度大于 1,将串 S 从中间分开,分为等长的左右子串 S1 和 S2;由左子串 S1 构造 R 的左子树 T1,由右子串 S2 构造 R 的右子树 T2。

现在给定一个长度为 2^N 的 01 串,请用上述构造方法构造出一棵 FBI 树,并输出它的后序遍历序列。

输入格式

第一行是一个整数 N(0≤N≤10),

第二行是一个长度为 2^N 的 01 串。

输出格式

一个字符串,即 FBI 树的后序遍历序列。

输入输出样例

输入 #1:

3
10001011

输出 #1:

IBFBBBFIBFIIIFF

说明/提示

对于 %40% 的数据,N≤2;

对于全部的数据,N≤10。

noip2004普及组第3题。

题目大意:

   题目大意就是给定一个序列,就比如10001011,我们将这个序列一直从中间分开,左边为左子树,右边为右子树,根据每一段全0全1还是10都有得到值F、B、I,构建成一棵二叉树,并且倒序输出

递归解法:

  这个解法比队列解法要简单许多。

[NOIP2004 普及组] FBI 树 递归解法icon-default.png?t=N4P3http://t.csdn.cn/YK8MW

队列解法:

如何得到一个二叉树的后序输出?

我们只需要得到这颗二叉树的层次遍历放在一个数组中即可,后序遍历代码如下
(大致思路就是先判断左子树是否存在,如果存在就遍历,然后遍历右子树,最后输出根节点)

void print(ll h) {
    ll l=h*2,r=h*2+1;
    if(l<=size) 
	  print(l);
    if(r<=size) 
	  print(r);
    cout<<S[h];
}

现在我们只需要得到这个二叉树的层次遍历数组即可

对于一开始这个序列,我们从中间分开,也就是分成1000 和 1011两部分,最开始这个既有0又有1,所以根节点是F,F的左子树是1000也就是F,右子树1011也是F,我们将这三个值依次存到层次遍历数组中。

接下来我们要遍历左子树分开两部分的值,注意如果是层次遍历的话,我们不能简单的用递归来解决,如果用递归的话,我们会一直递归到最深的那个叶节点之后再开始向右。

如下图,我们需要依次将10 00 10 11的值放入层次遍历数组,但是如果我们使用递归来解决两个范围的话,类似如下图中的代码,我们会先解决左边部分再解决右边部分。第一次分成10和00,然后进入10分成1 和 0存进数组之后出来进入右边的00,然后再进去存放两个0。

然而我们的需求是先把1000分开成两个值存入数组之后直接遍历1011,而不是继续遍历1000的子树,子树部分我们应该放在下一个范围解决

那么该怎么解决这个问题呢
我们分成1000和1011之后,不是要将这两个范围继续分小,然后存入层次遍历数组吗?那么我们可以将没有处理的存到一个数据结构中,1000分成10和00之后,我们暂时不处理这两个范围,将他们存入数据结构留到后面处理。
那么哪种数据结构适合存放数据呢,我们来思考一下存放的特点,先存进去的先处理,后存进去的后处理,而这刚好符合一种数据结构——队列,对于1000和1011,我们先处理1000,分成两部分10和00之后,我们暂时不处理这两个范围,将他们存入队列,之后再处理1011,分成10和11之后同理也是存入队列,这时队列的结构是这样的

分别将这四个对应的字母10(F) 00(B) 10(F) 11(I)存入层次遍历数组之后,我们从队首取得10,然后做进一步的处理,将处理之后的值继续存入队列表明下一层要处理的范围。

就这样直到队列中没有一个元素表明这颗FBI树已经全部构造完成,那么相应的层次遍历数组也得到了,我们再用上面的代码输出这棵树的后序即可

分析代码

对于队列,你可以使用stl库中的队列,在这里我自己用数组模拟了一个队列,当然你也可以手写一个队列
我们将最开始的范围也就是从1到2^N存入队列,这也是我们要处理的第一个元素

head指向我们当前队列的头部,tail是我们队列的尾部,也就是目前最后要处理的一个数据,队列从0开始计数,tail的位置没有数据,对于每一个范围有一个左边界一个有边界,所以我们定义一个结构体,每次把这个结构体存入队列

struct Node{
    ll l,r;
}Q[10020];

    ll h=-1,d=1,p,q;
    Q[++h].l=l;
    Q[h].r=r;

只要当前队列有元素我们就需要处理,所以循环的退出条件为head==tail(表明队列头已经到队列尾空的地方)

每次用head取到当前队列的头部,zero和one用来判断这个范围是全1还是全0(如果中间有一个1,那么全0 zero就为假,如果中间有一个0,那么全1 one就为假,最后如果两个都为假说明两个都有即为F,I和B依次类推)

处理完这个范围之后,我们将这个数据排出队列,然后插入层次遍历数组(cnt表示当前遍历数组的个数,初始为0),也就是head++,如果这个范围下面有子范围,那么我们将子范围分成两个部分继续插入队列尾部

    bool f1,f2;
    while(h<d){
        p=Q[h].l;
        q=Q[h].r;
        f1=f2=true;
        for(ll i=p;i<=q;i++){
            if(T[i]=='1') 
			  f1=false;
            else if(T[i]=='0') 
			  f2=false;
        }
        if(!f1&&!f2)
          S[++size]='F';
        else if(f2&&!f1)
          S[++size]='I';
        else if(f1&&!f2)
          S[++size]='B';
        h++;
        if(p<q){
            Q[d].l=p;
            Q[d++].r=(p+q)/2;
            Q[d].l=(p+q)/2+1;
            Q[d++].r=q;
        }
    }

得到层次遍历数组之后我们直接后序遍历即可

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,size;
char S[10020];
char T[1050];
struct Node{
    ll l,r;
}Q[10020];
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
          f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
void print(ll h) {
    ll l=h*2,r=h*2+1;
    if(l<=size) 
	  print(l);
    if(r<=size) 
	  print(r);
    cout<<S[h];
}
void FBI(ll l,ll r){
    ll h=-1,d=1,p,q;
    Q[++h].l=l;
    Q[h].r=r;
    bool f1,f2;
    while(h<d){
        p=Q[h].l;
        q=Q[h].r;
        f1=f2=true;
        for(ll i=p;i<=q;i++){
            if(T[i]=='1') 
			  f1=false;
            else if(T[i]=='0') 
			  f2=false;
        }
        if(!f1&&!f2)
          S[++size]='F';
        else if(f2&&!f1)
          S[++size]='I';
        else if(f1&&!f2)
          S[++size]='B';
        h++;
        if(p<q){
            Q[d].l=p;
            Q[d++].r=(p+q)/2;
            Q[d].l=(p+q)/2+1;
            Q[d++].r=q;
        }
    }
}
int main(){
    n=read();
    for(ll i=1;i<=pow(2,n);i++)
      cin>>T[i];
    FBI(1,pow(2,n));
    print(1);
    return 0;
}

 总结:

  此题较为简单,还有一种直接递归的方式可以实现。

题目链接:
[NOIP2004 普及组] FBI 树 - 洛谷https://www.luogu.com.cn/problem/P1087

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
2004年NOIP普及复赛数据是指在2004年举办的NOIP普及复赛中所使用的题目和数据。NOIP是指全国青少年信息学奥林匹克竞赛,旨在培养和选拔青少年的计算机编程能力。 2004年复赛中的数据包括了一系列的题目和测试用例。这些题目主要考察了参赛选手的编程基础和解题能力。每个题目都有一些特定的要求和限制条件。通过这些题目,考察选手对编程语言的掌握程度、算法的设计和实现能力、问题分析与解决的能力等。 复赛的数据往往是由工作人员根据题目要求设计和生成的。每个题目可能包含了多个测试用例,用于验证程序的正确性和性能。参赛选手需要编写程序解决这些题目,并保证程序能够正确地处理不同的测试用例。 在比赛中,选手需要根据题目描述和输入数据,编写相应的程序来求解问题。比赛规定了程序的运行时间和内存限制,选手需要在规定的时间和空间限制下编写高效的程序,并得到正确的输出结果。 通过参与2004年NOIP普及复赛,选手们在实际的编程竞赛中得到了锻炼和提高。他们学会了如何拆解问题、设计算法、编写代码,并在紧张的比赛环境下克服困难,迅速解决问题。 综上所述,2004年NOIP普及复赛数据是用于考察选手编程基础和解题能力的一系列题目和测试用例,选手需要根据题目描述和输入数据编写程序,以期在比赛中表现出色。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙星尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值