题目:
有诗云:
相思 (王维 唐)
红豆生南国, 春来发几枝。
愿君多采撷, 此物最相思。
那么,我们来采红豆吧!
假设红豆树是这个样子的:
这种红豆树的特点是:
- 每个结点都有一个正整数编号,标在结点内部。结点的编号各不相同。
- 最上方一层结点是 “
红豆
”(图中红圈所示的5个结点),这一层被称之为红豆层。 - 树的根结点、左子结点、右子结点、左子树、右子树等的定义与“数据结构”中的“二叉树”相同,但它毕竟是“自然界中的树”,树根在最下方,如图中的
结点5
- 图中这棵红豆树是“完全二叉红豆树”,类似“数据结构”中的“完全二叉树”。(“完全二叉树”的定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是完美二叉树。对于一个有N个结点的二叉树,若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点,这样的树就是完全二叉树) 从图上看,就是:要么每一层(包括红豆层)的结点数达到最大值,要么只在红豆层的最右边缺少一些结点。
对于红豆树,我们定义两种遍历顺序:
正序遍历
:先访问树根结点,再正序遍历
其左子树,最后正序遍历
其右子树逆序遍历
:先逆序遍历
其右子树,再逆序遍历
其左子树,最后访问树根结点
对于给定的一棵完全二叉红豆树
以及一些要采撷的结点,计算每次采撷能采到的红豆数量。
注意:我们采的点,可能是红豆,也可能不是红豆。采撷一个结点的意思是,把这个结点及这个结点的子树的全部结点从树中采下来。
例如:若采结点7,这是红豆结点,我们将获得1颗红豆;若采结点11,这不是红豆结点(而是一个枝结点!),我们将获得红豆树的一枝,包含2个红豆结点(8和2)。
输入格式:
输入有四行。
第一行是一个不超过60
的正整数N
,表示完全二叉红豆树中的结点数量。
第二行是N
个不超过1000
的结点编号序列,以空格间隔,表示的是这棵树的逆序遍历
序列。
第三行是一个不超过N
的正整数K
,表示进行K
次采撷。
第四行是K
个正整数,依次表示每次要采的结点编号。
输出格式:
输出包含K+1
行,
前K行,对于输入的每个采撷的点,在一行输出相应获得的红豆数量。如果这个点已经被采掉了,则输出Zao Jiu Cai Diao Le!
。如果这个点在原树中根本不存在,则输出Kan Qing Chu Le?
。
最后一行,输出采撷结束之后,这棵红豆树的正序遍历
序列,用空格分隔,最后一个结点之后没有空格。如果采撷结束之后树已空,则输出Kong Le!
输入样例1:
对于题目中给出的图,对应的输入是:
12
10 4 3 12 6 7 1 2 8 11 9 5
4
15 12 11 2
输出样例1:
Kan Qing Chu Le?
1
2
Zao Jiu Cai Diao Le!
5 9 1 7 6
输入样例2:
对于题目中给出的图,对应的输入是:
12
10 4 3 12 6 7 1 2 8 11 9 5
1
5
输出样例2:
5
Kong Le!
题意:
简单来说,就是给定一颗完全二叉树,最后一层的点是果子,然后有m次询问,给定某一个节点编号,需要把这个节点和它对应的子树都砍掉,并输出得到了多少个果子,如果该编号不存在或者已经砍掉了则特判输出即可。
思路:
首先明确我们要解决的问题
- 如何根据完全二叉树的"逆序遍历"(可以转化成后序遍历,左右根的顺序遍历),重建这颗二叉树
- 如何找到并标记所有的果子
- 如何判定给定节点编号是否在树中
- 如何判定给定节点编号是否已经被砍掉
- 如何模拟摘果子的过程
- 如何前序遍历这棵树并输出
首先我们要搞懂,为什么仅根据一次遍历结果就能够重建二叉树,因为这是一颗完全二叉树,它的空节点位置是固定的,都集中在最后一层和倒数第二层,而普通的二叉树却做不到这一点。还有我们上次做的,通过“#”标识空节点,也能够做到一次遍历重建二叉树。一般来说,完全二叉树用一个数组存就行了,和堆的存储一样。然后递归建树即可,注意这里和后序遍历区别在,是先遍历右子树,再是左子树最后是根节点,重建代码如下:
void build(int u)
{
if(u <= n) //下标不能越界
{
build(2 * u + 1); //先遍历右子树
build(2 * u); //然后是左子树
cin>>tree[u]; //tree数组用来存树
mp[tree[u]] = u; //mp用来标记某一节点是否在树中
}
}
其次如何标记果子,果子都在最后一层,果子之上每一层都是满的,从上到小每一层满的个数依次是:1,2,4,8……,计算每一层上面所有的节点个数(包含本层)依次是:1,3,7,15……这样我们就可以发现规律,我们按照满二叉树的节点个数进行计算,如果从第一层到该层的节点个数小于满二叉树所应有的节点个数,那么就可以判断该层就是最后一层,注意特判一下原本就是满二叉树的情况,代码如下:
int bits[] = {0 , 1 , 3 , 7 , 15 , 31 , 63}; //节点个数不超过60,所以开到63即可
int x = 0;
while(bits[x] < n) //统计最后一层满足满二叉树节点个数的层数
x++; //x层是满足条件的下一层
for(int i = bits[x - 1] + 1 ; i <= n ; i++) //从x-1层到树的最后一个节点,都是果子,用bool数组进行标记
isFruit[i] = true;
然后再说如何判断给定节点编号是否在树中,其实在重建树的时候就提到了,直接在建树的时候就开数组标记即可。
再说如何判断被砍掉,还记得我们重建的时候用tree数组去存树吗,由于每个节点编号都是正整数,那么我们在砍树的过程中,直接把要砍掉的节点tree的值置为零即可。
然后是重头戏,如何模拟砍树?首先明确砍树的过程,从一个节点出发,将自己和自己的子树全部砍掉,这里我们可以递归遍历去写,根据上面说的,砍树时,每走到一个点,就把这个节点对于的tree的值置为空。并且由于我们之前标记了果子,所以每走到一个果子节点,结果就加一,再递归地计算左子树的果子个数和右子树的果子个数即可,代码如下:
int pick(int u)
{
int cnt = 0; //初始化果子个数
if(u <= n && tree[u]) //下标没有越界并且该节点没被砍掉
{
if(isFruit[u]) //如果当前节点是果子,则个数加一
cnt++;
tree[u] = 0; //将该节点状态置为砍掉了
cnt += pick(2 * u); //递归计算左子树果子个数
cnt += pick(2 * u + 1); //递归计算左子树果子个数
}
return cnt; //返回本次砍树得到的果子个数
}
最后前序遍历输出就比较简单了,从根节点出发,先遍历左子树,再遍历右子树,最后是根节点,注意,这里要跳过被砍掉了的节点,代码如下:
void preTravel(int u)
{
if(u <= n && tree[u]) //下标不能越界并且没有被砍掉
{
ans.push_back(tree[u]); //将当前节点加入结果数组
preTravel(2 * u); //递归遍历左子树
preTravel(2 * u + 1); //递归遍历右子树
}
}
preTravel(1); //从根节点出发开始前序遍历
for(int i = 0 ; i < ans.size() ; i++)
cout<<ans[i]<<(i == ans.size() - 1 ? "\n" : " "); //避免行末空格输出
代码:
//L2-1 红豆生南国 (25 分)
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n , k , x;
int tree[N] , mp[N];
bool isFruit[N];
vector<int> ans;
int bits[] = {0 , 1 , 3 , 7 , 15 , 31 , 63};
void build(int u) //重建二叉树
{
if(u <= n)
{
build(2 * u + 1);
build(2 * u);
cin>>tree[u];
mp[tree[u]] = u;
}
}
int pick(int u) //摘果子
{
int cnt = 0;
if(u <= n && tree[u])
{
if(isFruit[u])
cnt++;
tree[u] = 0;
cnt += pick(2 * u);
cnt += pick(2 * u + 1);
}
return cnt;
}
void preTravel(int u) //前序遍历
{
if(u <= n && tree[u])
{
ans.push_back(tree[u]);
preTravel(2 * u);
preTravel(2 * u + 1);
}
}
int main()
{
cin>>n;
build(1);
int x = 0;
while(bits[x] < n)
x++;
for(int i = bits[x - 1] + 1 ; i <= n ; i++)
isFruit[i] = true;
cin>>k;
while(k--)
{
cin>>x;
if(!mp[x]) //节点不在树中
cout<<"Kan Qing Chu Le?"<<endl;
else if(!tree[mp[x]]) //该节点被砍掉了
cout<<"Zao Jiu Cai Diao Le!"<<endl;
else //模拟砍树
cout<<pick(mp[x])<<endl;
}
if(!tree[1]) //树根都被砍掉了
cout<<"Kong Le!"<<endl;
else
{
preTravel(1);
for(int i = 0 ; i < ans.size() ; i++)
cout<<ans[i]<<(i == ans.size() - 1 ? "\n" : " ");
}
return 0;
}