【NOIP模拟考三】线段树/ST表 day2 second 二叉树

题目描述

 给定一棵二叉树,节点标号从1到n。在不改变其中序遍历的情况下,请改变树的结构,使得这棵二叉树的先序遍历(前序遍历)字典序最小。

输入

第一行一个整数n,表示二叉树的节点数。
接下来n行,每行两个整数。第i行的两个整数表示编号为i的节点的左儿子和右儿子的编号(不存在即为0)。

输出

输出一行n个整数,表示不改变中序遍历的情况下字典序最小的前序遍历序列。

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

5
5 4
0 0
2 1
0 0
0 0

样例输出

1 2 3 5 4

提示


1     3        N/A


2     4        N/A


3    10        N/A


4    100       树为一条链,且只存在右儿子关系。


5    1000      给出的树满足排序二叉树的性质。即任意一个节点


6    100000    左子树中所有值<该节点<右子树中所有值。


7    65535     满二叉树


8    100000    N/A


9    100000    N/A



    看那水题一道两道三道连成线!

    咳咳,我还是要填坑的。。。

    考试时这道题还是想了很久,想过各种诡异的方法,比如LCA啦、平衡树啦、暴力啊。。。什么都有,后来才恍然大悟——这题好水。。。

    我们要让中序遍历的顺序不变,却不知道中序遍历的结果是什么,怎么可以呢,是吧……所以第一步应该中序遍历!

    我们就举样例的例子吧!


    好久没有看到图片了,是不是很激动?
    很容易发现,我们只需要让根最小就是最优的,那么我们很显然要1当根。其次,显然的,根的左儿子最小就最好,于是我们把2当做1的左儿子,但是,尽管样例是可以的,却并不代表所有数据都可以,别忘了中序遍历的限制!
    很容易发现,只要是中序遍历根左边的,就可以当成是左儿子,那么我们可以在根左边的中选出一个最小的来当根的左儿子,然后同样的在右边选一个最小的当右儿子,用线段树维护区间最小值就好了啦……(当然ST表也是可以的……)
    所以样例就很容易解释了——先选1,在左边选择最小的2,2没有左边,于是在右边选择最小的3,3也没有左边,于是在右边选择最小的5,这时1的左子树完毕,在右边选择最小的4,整棵树完毕。按遍历顺序依次输出:
    1 2 3 5 4
    PS:这道题虽然是求先序遍历,其实完全不需要再建一棵树,只要假想有那么一棵树就可以了……
    好了,一如既往的代码……
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
int n,ls[N],rs[N],fa[N],r;
int mid[N],wz[N],tim;
void dfs(int r)
{
    if(ls[r])
        dfs(ls[r]);
    mid[++tim]=r;
    wz[r]=tim;
    if(rs[r])
        dfs(rs[r]);
}
struct ST
{
    int l,r,minn;
}s[4*N-15];
void make(int l,int r,int q)
{
    s[q].l=l;s[q].r=r;
    if(l<r)
    {
        int mid=(l+r)>>1;
        make(l,mid,q<<1);
        make(mid+1,r,q<<1|1);
    }
}
void insert(int a,int k,int q)
{
    if(s[q].l==k&&s[q].r==k)
    {
        s[q].minn=a;
        return;
    }
    if(k>((s[q].l+s[q].r)>>1))
        insert(a,k,q<<1|1);
    else
        insert(a,k,q<<1);
    s[q].minn=min(s[q<<1].minn,s[q<<1|1].minn);
}
int minn(int l,int r,int q)
{
    if(l==s[q].l&&r==s[q].r)
        return s[q].minn;
    int mid=(s[q].l+s[q].r)>>1;
    if(r<=mid)
        return minn(l,r,q<<1);
    if(l>mid)
        return minn(l,r,q<<1|1);
    return min(minn(l,mid,q<<1),minn(mid+1,r,q<<1|1));
}
void getans(int l,int r)
{
    if(l>r)
        return;
    int now=minn(l,r,1);
    int p=wz[now];
    printf(" %d",now);
    getans(l,p-1);
    getans(p+1,r);
}
int main()
{
    //freopen("bitree.in","r",stdin);
    //freopen("bitree.out","w",stdout);
    scanf("%d",&n);
    make(1,n,1);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&ls[i],&rs[i]);
        if(ls[i])
            fa[ls[i]]=i;
        if(rs[i])
            fa[rs[i]]=i;
    }
    for(int i=1;i<=n;i++)
        if(!fa[i])
        {
            r=i;
            break;
        }
    dfs(r);
    for(int i=1;i<=n;i++)
        insert(mid[i],i,1);
    printf("1");
    getans(1,wz[1]-1);
    getans(wz[1]+1,n);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值