zoukankan      html  css  js  c++  java
  • 洛谷 P1377 [TJOI2011]树的序 解题报告

    P1377 [TJOI2011]树的序

    题目描述

    众所周知,二叉查找树的形态和键值的插入顺序密切相关。准确的讲:1、空树中加入一个键值(k),则变为只有一个结点的二叉查找树,此结点的键值即为(k);2、在非空树中插入一个键值(k),若(k)小于其根的键值,则在其左子树中插入(k),否则在其右子树中插入(k)

    我们将一棵二叉查找树的键值插入序列称为树的生成序列,现给出一个生成序列,求与其生成同样二叉查找树的所有生成序列中字典序最小的那个,其中,字典序关系是指对两个长度同为(n)的生成序列,先比较第一个插入键值,再比较第二个,依此类推。

    输入输出格式

    输入格式:

    第一行,一个整数,(n),表示二叉查找树的结点个数。第二行,有(n)个正整数,(k_1)(k_n),表示生成序列,简单起见,(k_1)~(k_n)为一个1到(n)的排列。

    输出格式:

    一行,(n)个正整数,为能够生成同样二叉查找数的所有生成序列中最小的。

    说明

    对于20%的数据,n ≤ 10。

    对于50%的数据,n ≤ 100。

    对于100%的数据,n ≤ 100,000。


    先化简一下模型,我们把(BST)树建好,然后输出中序遍历即是答案。

    然而,直接建(BST)是很容易退化成链的。

    做过题目多的人可能听说过,这个叫笛卡尔树

    笛卡尔树是一种既满足堆性质又满足二叉排序树性质的树。

    方法一:普通笛卡尔树的建树方法

    现在有一个序列按二叉排序树的关键字(Key)从小到大有序,序列中包含小跟堆的关键字(Index)关键字,在(O(N))的时间建树。

    按原数列顺序一个一个将节点加入笛卡尔树,可以确定一定是从链的右边向下找到一个(Index)大于这个点的节点,把这个节点位置占据,然后置让Ta当自己的左儿子即可。

    用栈把最右边的链存下来,栈顶为右边最底的那个点,加入时边弹出边向上找,更新好位置关系后把自己存进栈即可。

    code:

    #include <cstdio>
    #include <algorithm>
    #define ls t[now].ch[0]
    #define rs t[now].ch[1]
    #define f t[now].par
    const int N=100010;
    struct node
    {
        int dat,index;
        bool friend operator <(node n1,node n2)
        {
            return n1.dat<n2.dat;
        }
    }a[N];
    struct BST
    {
        int ch[2],dat,index,par;//左右儿子,BST域,堆域,父亲
    }t[N];
    int tot=0,n,s[N];
    void connect(int fa,int now,int typ)
    {
        f=fa;
        t[fa].ch[typ]=now;
    }
    void dfs(int now)
    {
        if(!now) return;
        printf("%d ",t[now].dat);
        dfs(ls);
        dfs(rs);
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",a+i);
            a[i].index=i;
        }
        std::sort(a+1,a+1+n);
        for(int i=1;i<=n;i++)
        {
            int last=0;
            while(tot&&t[s[tot]].index>a[i].index)
                last=tot--;
            t[i].dat=a[i].dat;
            t[i].index=a[i].index;
            connect(s[tot],i,1);
            connect(i,s[last],0);
            s[++tot]=i;
        }
        dfs(t[0].ch[1]);
        return 0;
    }
    
    

    方法二:按堆性质自底向上建立。
    这个方法好厉害,真的很强。

    则如果两个点的(Key)在“当前”数值上的差最小,那么Ta俩一定有一条边。

    (Index)从大到小建立笛卡尔树,则对(1)-(Index-1)的点中,是不会有(Index)的儿子的。

    (pre[i])(suc[i])分别存储(Key)(i)的点在“当前”所相邻的前驱和后继。

    每次处理完一个点,更新与它相连的“前驱”和“后继”,这对应了“当前”

    注意到原数据为1到n的排列,可以用桶排,比较快。

    Code:

    #include <cstdio>
    const int N=100010;
    int a[N],b[N],L[N],R[N],pre[N],suc[N],n;
    void dfs(int now)
    {
        if(!now) return;
        printf("%d ",a[now]);
        dfs(L[now]);dfs(R[now]);
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",a+i);
            b[a[i]]=i;
            pre[i]=i-1,suc[i]=i+1;
        }
        for(int i=n;i>1;i--)
        {
            int pree=pre[a[i]],succ=suc[a[i]];
            if(b[pree]>b[succ])
                R[b[pree]]=i;
            else
                L[b[succ]]=i;
            suc[pree]=succ;pre[succ]=pree;
        }
        dfs(1);
        return 0;
    }
    
    

    2018.6.19

  • 相关阅读:
    Queue Aizu
    Stack Aizu
    Stack Aizu
    Shell Sort Aizu
    Shell Sort Aizu
    Stable Sort Aizu
    VS2013下.Net Framework4配置FineUI4.14
    VS2013下.Net Framework4配置FineUI4.14
    Ubuntu-14.04.1 desktop安装时遇到的小问题
    Ubuntu-14.04.1 desktop安装时及安装后遇到的小问题
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9199591.html
Copyright © 2011-2022 走看看